2

The set_time() function is supposed to take a particular time and add the specified number of minutes to this time and save it in *t.

#include <stdio.h>
#include <time.h>

enum {JAN, FEB, MAR, APR, MAY, JUNE, JULY, AUG, SEP, OCT, NOV, DEC};

void set_time(struct tm *t,
              int year, int mon, int day,
              int hour, int min, int sec,
              int mins_to_add)
{
    /* Copy all the values. */
    t->tm_year = (year - 1900);
    t->tm_mon = mon;
    t->tm_mday = day;
    t->tm_hour = hour;
    t->tm_min = min;
    t->tm_sec = sec;

    // No DST since we are storing UTC time.
    t->tm_isdst = 0;

    /* Add minutes to time. */
    t->tm_min += mins_to_add;
    mktime(t);

    /* Debug print */
    printf("%s\n", asctime(t));
}

int main(int argc, char **argv)
{
    struct tm t;
    set_time(&t, 2011, AUG, 1, 10, 00, 00, 0);
    return 0;
}

So, I ask set_time() to add nothing to Aug 01 10:00:00 2011. However, if my Windows system is set to EST time zone, the output I get is: Mon Aug 01 11:00:00 2011

The error happens because I do the task of adding the specified number of minutes (0 in the above example) to the specified time (Aug 01 10:00:00 2011 in the above example) with this part of the code:

    /* Add minutes to time. */
    t->tm_min += mins_to_add;
    mktime(t);

mktime() is called to adjust all other member variables of the structure in case t->tm_min exceeds 59 due to the addition. But mktime() treats the time in struct tm object as local time. So, when it sees that the date is Aug 01 2011 and local time zone as EST, it assumes that DST needs to be applied but it finds that t->tm_isdst is set to 0 (meaning DST isn't applied to the time in *t). So, it applies DST, adjusts the date from Aug 01 10:00:00 2011 to Aug 01 11:00:00 2011 and sets t->tm_isdst to 1.

Had I initialized t->tm_isdst to 1, this issue wouldn't have occurred because mktime() would have found that the time in *t already has DST applied to it. However, it would have messed up the result if I asked set_time() to add nothing to a date on which DST should not not applied, say Jan 01 10:00:00 2011. Now, it would try to turn off DST in *t thereby setting t->tm_isdst to 0 and re-adjusting the date to Jan 01 09:00:00 2011.

As a result, this has the undesirable effect of adding an extra hour to or subtracting an extra hour from the specified time when I wanted to add nothing to it. This happens because when mktime() finds that the DST setting in t->tm_isdst is not what it should be as per the local time-zone of the system, it tries to set it correctly by readjusting the time in *t.

What is the right way to solve this problem?

Susam Pal
  • 32,765
  • 12
  • 81
  • 103
  • 1
    Closely related, arguably a dupe: http://stackoverflow.com/questions/530519/stdmktime-and-timezone-info. – ruakh Jan 27 '12 at 23:43
  • ruakh, The post you mentioned is closely related but not similar. The problem there has a neat workaround which involves subtracting the time-zone offset from the timestamp returned by `mktime()`. However, my problem is about adding certain number of minutes to a time in `struct tm` object and making sure that `mktime()` doesn't add or subtract anything extra from the date because of DST settings. – Susam Pal Jan 27 '12 at 23:52

2 Answers2

2

The documentation on struct tm says

The Daylight Saving Time flag (tm_isdst) is ... less than zero if the information is not available.

I suggest you set it to -1 and see if that fixes things.

Borodin
  • 126,100
  • 9
  • 70
  • 144
  • 1
    This may still have problems around DST transitions, for example `set_time(&t, 2011, MAR, 13, 01, 59, 00, 1);` with a local time zone of PST will return 03:00:00. – DRH Jan 28 '12 at 00:21
  • 1
    @Borodin Yes, however my comment was incomplete. The original code would return `03:00:00`. The updated code (using `tm_isdst = -1`) returns `01:00:00`. – DRH Jan 28 '12 at 01:08
  • DRH, You are right. That's a very good catch. I tried `set_time(&t, 2011, MAR, 13, 02, 30, 00, 0);` for EST and the output was: `Sun Mar 13 01:30:00 2011` because 2:30 AM is not a valid time on March 13, 2011 because the clock jumps from 02:00 AM to 03:00 AM directly on that date. This also holds true for the example you have given. Thanks for noticing this and making us aware of it. – Susam Pal Jan 28 '12 at 01:10
1

The Windows function _mkgmtime can be used instead of mktime order to perform this conversion safely (regardless of the local time zone settings).

DRH
  • 7,868
  • 35
  • 42
  • DRH, The documentation at http://msdn.microsoft.com/en-us/library/2093ets1(v=vs.80).aspx mentions: "When specifying a tm structure time, set the tm_isdst field to: Zero (0) to indicate that standard time is in effect, a value greater than 0 to indicate that daylight saving time is in effect, a value less than zero to have the C run-time library code compute whether standard time or daylight saving time is in effect." - I always get the same answer irrespective of what I initialize `t->tm_isdst` to. So, I'm unable to understand how exactly does this function make use of `tm_isdst`. – Susam Pal Jan 28 '12 at 01:21
  • @Susam Pal I'm not 100% however I believe that documentation is lifted from their documentation for `mktime`, and doesn't actually apply to `_mkgmtime`. That's based on various experiments with both setting `tm_isdst` to various values as well as setting my local time to in and out of DST, in all cases the result `tm_isdst` is set to `0` (which is what I would expect for UTC), – DRH Jan 28 '12 at 01:29
  • And for those of you who are on Unix, there is `timegm` which is the inverse of `gmtime` (and `timelocal`, the inverse of `localtime`, which is equivalent to `mktime`). – musiphil Jun 20 '12 at 22:54