Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux-x64 glibc: Why does Feb 1 come before Jan 31?

When you call mktime(), Feb 1 seems to come before Jan 31. Why is this? Am I doing something wrong or is this a bug in glibc?

Here's the code:

struct tm tm;
time_t tt;

memset(&tm, 0, sizeof(tm));
tm.tm_year = 2011;
tm.tm_mon = 1;
tm.tm_mday = 31;
tm.tm_hour = 11;
tm.tm_min = 41;
tm.tm_sec = 28;
tm.tm_isdst = 0;
tt = mktime(&tm);

printf("Time now %d-%d-%d %d:%d:%d (%s) = %lu\n",
    tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tt);


memset(&tm, 0, sizeof(tm));
tm.tm_year = 2011;
tm.tm_mon = 2;
tm.tm_mday = 1;
tm.tm_hour = 1;
tm.tm_min = 1;
tm.tm_sec = 1;
tm.tm_isdst = 0;
tt = mktime(&tm);

printf("Time now %d-%d-%d %d:%d:%d (%s) = %lu\n",
    tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_zone, tt);

And here's the output:

Time now 2011-2-3 11:41:28 (PST) = 61257325288
Time now 2011-2-1 1:1:1 (PST) = 61257114061

Note that the original intention was to compare two time_t's. This issue causes the first date/time to appear to be later than the second, which is obviously a bit of a problem.

This is just compiled with "gcc test.c" and run with "./a.out" on Ubuntu 9.10, gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu8), libc-2.10.1-0ubuntu15

On a 32-bit system the results are as expected - i.e. completely different to the 64 bit result!

Would anyone care to confirm/refute this result and/or give some insight into what I may be doing wrong?

like image 430
Paul A Avatar asked Feb 02 '11 20:02

Paul A


2 Answers

tm_mon is zero-based, so you attempted to set February 31st, which got normalized. Here's a link to the definition of mktime().

like image 168
Jim Garrison Avatar answered Oct 31 '22 14:10

Jim Garrison


Why does Feb 1 come before Jan 31?

Incorrect assignments to struct tm members.


.tm_mon is zero based

.tm_mon is zero based@Jim Garrison.

// tm.tm_mon = 1;
tm.tm_mon = 1 - 1; // For January

.tm.tm_year is 1900 based

// tm.tm_year = 2011;
tm.tm_year = 2011 - 1900;

time_t print specifier

"%ld" is not certainly the matching specifier for time_t, thus incurring undefined behavior (UB). time_t might not not even be an integer type. Recommend a cast to a wide signed type or this. Note that tt = mktime(&tm) returns a -1 value on error, so useful to see -1 and not an unsigned value.

// printf("%lu\n", tt);
printf("%lld\n", (long long) tt);

.tm_isdst

mktime() operates on local time. tm.tm_isdst = 0; asserts the time stamp is standard time (reasonable for PST about January). Had this code been run in a timezone with daylight time in January (e.g. Wellington), the reported tm.tm_hour may differ from expectations. Usually best to let mktime() deduce .tm_isdst. Otherwise finding the difference between timestamps (OP's higher goal) may shift an hour unexpectedly when spanning a DST change.

// tm.tm_isdst = 0;
tm.tm_isdst = -1;  // DST information is not available, mktime will adjust.
tt = mktime(&tm)
like image 45
chux - Reinstate Monica Avatar answered Oct 31 '22 15:10

chux - Reinstate Monica