Convert a summer date to utc in the winter? - c

The function mktime takes a struct tm as argument. One of the members of struct tm is tm_isdst. You can set this to 1 for wintertime, 0 for summertime, or -1 if you don't know.
However, if during winter, you try to convert 2009-09-01 00:00, mktime fails to see that although it is currently winter, the date you are converting is summertime. So the result is one hour off. For me (GMT+1) it's 2009-08-31 22:00, while it should be 23:00.
Is there a way to determine if a particular date is in the summer or wintertime period? Is it possible to convert a summer date to utc in the winter?
(I hit upon this problem trying to answer this question)

This is one of the (many) deficiencies in the time handling interfaces in Standard C. See also the Olson time zone database. There isn't an easy way to find when a time zone switches between winter and summer (daylight saving and standard) time, for example. Anything in the future is, of course, a prediction — the rule sets change often (the current release is 2017a).
Is there as far as you know, a UNIX specific solution?
I took a look at the code in tzcode2017a.tar.gz, and the mktime() there behaves as you want if you set the tm_isdst to -1 (unknown). So, if you use that (public domain) code, you would be OK - probably. Quoting from 'localtime(3)' as distributed with the Olson code:
Mktime converts the broken‐down time, expressed as local time, in the structure pointed to by tm into a calendar time value with the same encoding as that of the values returned by the time function. The original values of the tm_wday and tm_yday components of the structure are ignored, and the original values of the other components are not restricted to their normal ranges. (A positive or zero value for tm_isdst causes mktime to presume initially that summer time (for example, Daylight Saving Time in the U.S.A.) respectively, is or is not in effect for the specified time. A negative value for tm_isdst causes the mktime function to attempt to divine whether summer time is in effect for the specified time; in this case it does not use a consistent rule and may give a different answer when later presented with the same argument.)
I believe that the last caveat about a 'consistent rule' means that if the specification of the time zone changes (for example, as when the USA changed from 1st week in April to 2nd week in March for the change to Daylight Saving Time) mean that if you determined some time before the rule changed and after the rule changed, the same input data would give different outputs.
(Note there are useful HTML files in ftp://ftp.iana.org/tz/ directory, such as ftp://ftp.iana.org/tz/tz-link.html.)

Related

Is there any time.h function to get the date 14 days?

In my case I need to get the current date and the date 14 days later as a time limit for returning a book. Is there any such function or a manipulation to an existing function?
Given a broken-down time structure, struct tm, the Standard C (and POSIX) function mktime() will return a time_t value associated with the value. Further, the elements of the structure passed in can be out of their normal ranges, and mktime() will normalize them. So, you could add 14 to the tm_mday element of the structure, and mktime() will both give you the time_t value for the corresponding time and also normalize the elements of the structure, thus giving you the correct day of the month, month of the year, and year number.

DST transitions vs. iCalendar reminders vs. RFC 5545 duration spec

How should an RFC 5545 duration of less than 24 hours act when it crosses a Daylight Saving Time (DST) transition?
For example, assume DST ends at 2:00AM on a particular day, and assume that an event starts at the second 1:10AM on that day. (The first 1:10AM is one real-world hour earlier in Daylight Time, the second 1:10AM is in Standard Time).
If that event has a -PT15M (15 mins before) duration reminder, then how many real-world minutes before the start of the event should that reminder pop up? Should it pop up 15 real-world minutes earlier at the first 1:55AM (in Daylight Time)? Or should it pop up 75 real-world minutes earlier at 12:55AM Daylight Time?
The spec seems to imply the latter behavior, but that behavior seems counter-intuitive to me. If I want a reminder 15 minutes beforehand, I really mean "15 minutes". However it is intuitive for longer reminders, e.g. -P1D should be 25 hours before the start of that event so that you get the reminder at the same local time on the previous day.
Anyway, how do most calendar apps deal with this unintuitive behavior? Do they ignore the spec and always treat reminders <24 hours as exact without adjusting for DST? Or am I understanding the spec incorrectly and it's the -P1D case that will be unintuitive with the one-day reminder showing up at a different local time as the event start time?
Obviously this is somewhat of a corner case because few meetings start in the middle of the night. But there are cases where this could happen, e.g. late-night social events. And if I promise to meet you at the bar in 2 hours, I probably don't mean 1 hours or 3 hours on some nights!
Here's what the RFC 5545 spec says:
In the case of discontinuities in the time scale, such as the change from standard time to daylight time and back, the computation of the exact duration requires the subtraction or addition of the change of duration of the discontinuity.
To clarify further, according to https://standards.calconnect.org/csd/cc-51003.pdf, RFC 5545 has a concept of nominal durations (in local clock time) and exact durations (a real-world stopwatch that ignores DST). The language above is about how a nominal persisted duration should be converted to an exact duration.
This question isn't specific to a particular calendar implementation, but I'm tagging Google Calendar and Outlook here because developers using those APIs probably have the most context about these issues.
Consolidation of answers and comments above and direct answers to questions posed:
First part:
"How should an RFC 5545 duration of less than 24 hours act when it
crosses a Daylight Saving Time (DST) transition?"
Answer:
Since the duration is less than 24 hours, the exact number of hours, minutes, or seconds should be used for the duration (realtime). There is no confliction or ambiguity with durations < 24 hours.
Second part:
"If that event has a -PT15M (15 mins before) duration reminder, then
how many real-world minutes before the start of the event should that
reminder pop up? Should it pop up 15 real-world minutes earlier at the
first 1:55AM (in Daylight Time)? Or should it pop up 75 real-world
minutes earlier at 12:55AM Daylight Time?"
Answer:
15 real world minutes. The specs clearly say that anything less than one day is exact time. Any confusion or ambiguity around a daylight saving cutoff where a time is 'repeated' is avoided by adhering to the standard for referring a repeated clocktime during daylight savings. IE:
IE the first instance of a time such as 1.10am is the one that is assumed to be intended when one sees 1.10am for a timezone.
If one wants to refer to the second instance of 1.10 am one must use another timezone (eg UTC) to clearly identify the actual moment in time.
Third Part:
"how do most calendar apps deal with this unintuitive behavior? Do
they ignore the spec and always treat reminders <24 hours as exact
without adjusting for DST? Or am I understanding the spec incorrectly
and it's the -P1D case that will be unintuitive with the one-day
reminder showing up at a different local time as the event start
time?"
Answer:
They don't ignore the spec. The specification deals with this as one would intuitively expect. A reminder of < 24 hours (PT24H) is exact. No need to adjust for DST.
Similarly a reminder of 24 hours (PT24H) before should occur exactly 24 hours before.
In the case of a one day (P1D) reminder, a 1 day reminder should occur at the same time the previous day be this 23,24,or 25 hours before. This is as one would intuitively expect. The actual number of hours will of course vary depending on the day's place in the calendar.
P1D is not the same as PT24H, as P1D will depend on the dates place in the calendar, whether DST changeover is happening that day, whereas PT24H is exact.
References:
Daylight saving time and time zone best practices
https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5 see DATETIME
https://icalendar.org/iCalendar-RFC-5545/3-3-6-duration.html
https://icalendar.org/iCalendar-RFC-5545/3-8-2-5-duration.html
The challenge here is to avoid ambiguity, not just with duration, but also with the event time. See the first point in the summary of answers here Daylight saving time and time zone best practices
To avoid ambiguities and to accurately refer to the 'second' 1.10am one should use UTC timezone for those second instances.
Note: In the DATETIME part of the spec, under Form #3, https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5 it clearly states that a local time with timezone always refers to the first instance.
Page 45 of the above doc says for recurrences it is always interpreted as above (first instance):
If the computed local start time of a recurrence instance does not
exist, or occurs more than once, for the specified time zone, the
time of the recurrence instance is interpreted in the same manner
as an explicit DATE-TIME value describing that date and time, as
specified in Section 3.3.5.
I take this to mean that the only way to refer to the second 1.10 am of your example would be using UTC time! (or another timezone not affected by that daylight saving change).
The summary of answers in the stack overflow link above does note that in some cases, one may need to store both local & UTC. I suggest this may be exactly such a situation, where during the repeated cutover hour, the only truly accurate representation of times in that repeated daylight saving hour (the second hour), is the UTC time.

Why does mktime() need to know about daylight saving time?

So mktime() returns a time_t value which is defined as "an integral value representing the number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC" (source). I can convert a date to a time_t value by using mktime(). For example, to convert the date 10-Sep-2017 08:34:56 to a time_t I'd do the following:
struct tm tm;
time_t tv;
tm.tm_sec = 56;
tm.tm_min = 34;
tm.tm_hour = 8;
tm.tm_mday = 10;
tm.tm_mon = 8;
tm.tm_year = 117;
tm.tm_isdst = ????;
tv = mktime(&tm);
Now what I don't understand is the idea behind the tm_isdst parameter: It is described as "a flag that indicates whether daylight saving time is in effect at the time described" (source).
This description is somewhat confusing me because I'm of the opinion that the time I'm describing in struct tm is in fact already a UTC time and the time_t value I want to have from mktime() is UTC as well. But UTC time doesn't change with a change of seasons, so why does mktime() need to bother with daylight saving time at all? Isn't the advantage of using UTC instead of local time that I won't have to bother with daylight saving time? So why do I have to set tm_isdst then?
I'm sure the answer is really simple but at the moment I'm failing to see it. Could somebody please provide a simple example that illustrates why mktime() needs the tm_isdst parameter to convert a certain date and time to a time_t value?
Whether DST is in effect changes what the epoch time will be because mktime uses the current time zone to determine the time.
For example, if I populate tm with 1/1/70 00:00:00 as follows:
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 0;
tm.tm_mday = 1;
tm.tm_mon = 0;
tm.tm_year = 70;
tm.tm_isdst = 0;
I get a value of 18000 for tv because my timezone is GMT-5 (18000 = 3600 * 5). If I change the value of tm_isdst to 1, then tv will be set to 14400 (3600 * 4).
Setting tm_isdst to -1 will look at the local timezone database to see if DST is in effect for the given date/time.
Now what I don't understand is the idea behind the tm_isdst parameter:
It is described as "a flag that indicates whether daylight saving time
is in effect at the time described" (source).
This description is somewhat confusing me because I'm of the opinion
that the time I'm describing in struct tm is in fact already a UTC
time
Well, that's your main problem. Per its documentation, which you already linked, mktime() converts a time structure expressed as local time to a time_t.
and the time_t value I want to have from mktime() is UTC as well.
Again according to the docs, when a time_t is interpreted as an absolute time, it represents the number of seconds elapsed since the Epoch, in UTC, so that's indeed what you will get from mktime(). But that means that there is, in general, a time zone and possibly daylight-saving difference between the two representations.
The C library relies on contextual localization data for the time zone applicable to local time, but although it knows the current rules for daylight saving time, it does not necessarily know the rules that were in effect at the specified time. Moreover, there are edge cases at the changeovers between daylight time and standard time. For these and perhaps other reasons, you need to tell mktime() whether the time given refers to daylight saving time or standard time.
But UTC time doesn't change with a change of seasons, so why does
mktime() need to bother with daylight saving time at all?
Because mktime() accepts local time as input.
Isn't the
advantage of using UTC instead of local time that I won't have to
bother with daylight saving time?
That is one of the advantages, yes.
So why do I have to set tm_isdst
then?
Because mktime() accepts local time as input.
struct tm can be thought of as a timestamp.
Example: When a timezone goes from daylight time to standard time, the "day" has 25 hours and struct tm needs more info than just "Y-M-D h:m:s".
The local time may be 2:30 AM daylight time and then 1 hour later it is 2:30 AM standard time. .tm_isdst is especially useful to distinguish that case. Note that .tm_isdst is relevant in all timestamps.
As struct tm itself does not contain the timezone (that info exist elsewhere), .tm_isdst is designed to provide time stamps indication of standard time or daylight time (or even unknown standard/daylight).
If the .tm_isdst is set to -1 (unknown) and the timezone is UTC, then the expectation is that, for all timestamps, it is as if .tm_isdst == 0.

Day of week from seconds since epoch

I'm trying to calculate the day of week from a seconds since epoch timestamp. From time.h I can use gmtime(), but this increases the program with 1.3kB, probably because gmtime() also calculates the date.
So, I'm wondering what would be wrong about using something like:
(timestamp / (24*3600)) % 7
The only thing I could think of is leap seconds, such that something like 00:00:02 could be classified as the wrong day.
edit
This is for embedded programming, 1.3kB is a substantial part of the 64kB program/firmware. Furthermore, I'm not expecting to do anything with timezones or dates after this.
what would be wrong about using something like: (?)
(timestamp / (24*3600)) % 7
Not much wrong with the above except you have not specified the day of the week the epoch began (e.g. Thursday) nor the day of the week the week begins on (e.g. Monday). See 8601 for a deeper discusison on the first day of the week. Also watch out for computations like 24*3600 that with 16-bit int/unsigned lead to troubles.
Example: Let us say the epoch began on day-of-the-week number 3 (Monday:0. Thursday:3). This takes care of 2 issues at once: day of the week of the epoch and the first day of the week as only the positive difference needs to be coded.
#define EPOCH_DOW 3
#define SECS_PER_DAY 86400
dow = ((timestamp / SECS_PER_DAY) + EPOCH_DOW) % 7;
If timestamp is a signed type, append the following to insure a result in the [0...6] range.
dow = (dow + 7) % 7;
// or
if (dow < 0) dow += 7;
I doubt leap seconds are used in your application. If they are, the task is far more complicated as code then needs to deal with not only with a more complex calculation, but how to receive updates of the next scheduled leap-seconds - they occur irregularly.
You're off by 4, since January 1, 1970 was a Thursday, and you may compute an incorrect result for dates before the epoch depending on the behavior of % on negative operands. (The simplest fix is to check whether the result is negative, and if so add 7.) But other than this, your algorithm is correct; it's the same as used by glibc internal function __offtime, which gmtime ultimately ends up calling. (You can't call it yourself, as it's an internal implementation detail).
There's no need to worry about leap seconds; Unix time ignores them.
I would recommend encapsulating the code in a (possibly inline) function, with the gmtime implementation as a comment of #if 0 block, so that you can easily switch to it if you start needing to compute months/years as well.

Best way to convert Unix time_t to/from abitrary timezones?

My code receives a time_t from an external source. However, that time_t isn't acutally based on UTC Epoch time, along with it I get a timezone string (eg, EDT, PST, etc), and its based on this offset'ed epoch. I need to convert this to a true UTC Epoch based time_t.
Additionally, I need to be able to go in the opposite direction, taking a UTC Epoch based time_t and a timezone, and create the offsetted time_t, but in this situation instead of having a timezone like EDT/PST), etc, I have a Unix style timezone description like "America/New York" and need to convert to the correct timezone given daylight savings, so I'd need to get back from the algorithm, both an offsetted time_t, and the correct descriptor (EDT,EST).
I'm pretty sure I can pull this off by temporarily changing tzset() and some combination of conversions between broken-down time, time_t and timeval, but doing this always makes my brain hurt and makes me feel like I'm missing something obvious. Can anyone recommend some code or sudo-code to do this or at least a proper approach?
time_t is in seconds, so just offset your time_t values by 3600 times the number of hours the timezone is offset by. As long as you have the offset specifically identified (i.e. EST or EDT instead of US/Eastern or EST5EDT or whatnot) then this is really simple and not prone to errors.
would any of these functions help you? localtime(), gmtime(), etc.

Resources