Search code examples
cdstmktime

Strange mktime() behaviour


Why mktime() convert 31/03/2019 02:00 to 01:00 with tm.tm_isdst = 1 with CET timezone?

I think this is an invalid combination.

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

void reset(struct tm* tm){
    (*tm) = (const struct tm){0};

    tm->tm_sec = 0;
    tm->tm_min = 1;
    tm->tm_hour = 2;
    tm->tm_mon = 2;
    tm->tm_mday = 31;
    tm->tm_year = 2019 - 1900;
}

int main(int argc, char **argv)
{
    struct tm   tm;
    int secs;

    reset(&tm);
    printf("Before mktime\n");
        printf(" %04d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec);

    tm.tm_isdst = 0;
    secs = mktime(&tm);
                printf("After mktime tm.tm_isdst = 0;\n");
    printf(" %04d-%02d-%02d %02d:%02d:%02d TZ=%s , tm_isdst = %d, timestamp=%i\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tm.tm_isdst, secs);

    reset(&tm);
    tm.tm_isdst = 1;
    secs = mktime(&tm);
    printf("After mktime tm.tm_isdst = 1;\n");
    printf(" %04d-%02d-%02d %02d:%02d:%02d TZ=%s , tm_isdst = %d, timestamp=%i\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tm.tm_isdst, secs);

    reset(&tm);    
    tm.tm_isdst = -1;
    secs = mktime(&tm);
    printf("After mktime tm.tm_isdst = -1\n");
    printf(" %04d-%02d-%02d %02d:%02d:%02d TZ=%s , tm_isdst = %d, timestamp=%i\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tm.tm_isdst, secs);

    return 0;
}

Output:

% date
Sun Mar 31 06:50:42 CEST 2019
% test_mktime
Before mktime
 2019-03-31 02:01:00
After mktime tm.tm_isdst = 0;
 2019-03-31 03:01:00 TZ=CEST , tm_isdst = 1, timestamp=1553994060
After mktime tm.tm_isdst = 1;
 2019-03-31 01:01:00 TZ=CET , tm_isdst = 0, timestamp=1553990460
After mktime tm.tm_isdst = -1
 2019-03-31 03:01:00 TZ=CEST , tm_isdst = 1, timestamp=1553994060

Solution

    1. 2019-03-31 02:01:00 CET (is_dst = 0, UTC+1) doesn't exist, but is equivalent to 2019-03-31 01:01:00 UTC (1553994060 seconds after the epoch), which is equivalent to 2019-03-31 03:01:00 CEST (is_dst = 1, UTC+2).
    2. 2019-03-31 02:01:00 CEST (is_dst = 1, UTC+2) doesn't exist, but is equivalent to 2019-03-31 00:01:00 UTC (1553990460 seconds after the epoch), which is equivalent to 2019-03-31 01:01:00 CET (is_dst = 0, UTC+1). (Note: 1553994060 - 1553990460 = 3600, which is a difference of 1 hour.)
    3. For is_dst = -1 the implementation tries to figure out whether daylight saving time is in effect, but since 2019-03-31 02:01:00 is equally invalid for both CET and CEST, it just picks one or the other. In this case, it assumed the input was in standard time 2019-03-31 02:01:00 CET (is_dst = 0, UTC+1), which doesn't exist, but is equivalent to 2019-03-31 01:01:00 UTC (1553994060 seconds after the epoch), which is equivalent to 2019-03-31 03:01:00 CEST (is_dst = 1, UTC+2).