Error in mktime () on CentOS?

I played with mktime and I noticed strange and inconsistent behavior.

I give it a date that is absent during daylight saving time (DST), but with tm_isdst set to 1, which usually mktime makes tm_isdst change to 0 and adjusts the time accordingly, shifting by 1 hour.

However, in a period of time, approximately in 1928. 1933 (could not find another range), the behavior is different. The tm_isdst field is set to 0, but the time does not change. This leads to weirdness when doing time calculations, etc.

I have a tiny test program that prints for a given input date: the original struct tm, struct tm after calling mktime on it, the result of mktime and struct tm, which is the result of calling localtime on the result mktime (should represent the same point in time as source).

Output:

2013-01-01 12:00:00 (off=0, dst=1) -> 2013-01-01 11:00:00 (off=-28800, dst=0) -> 1357066800 -> 2013-01-01 11:00:00 (off=-28800, dst=0) 1927-01-01 12:00:00 (off=0, dst=1) -> 1927-01-01 11:00:00 (off=-28800, dst=0) -> -1356930000 -> 1927-01-01 11:00:00 (off=-28800, dst=0) 1929-01-01 12:00:00 (off=0, dst=1) -> 1929-01-01 12:00:00 (off=-28800, dst=0) -> -1293768000 -> 1929-01-01 12:00:00 (off=-28800, dst=0) 1932-01-01 12:00:00 (off=0, dst=1) -> 1932-01-01 12:00:00 (off=-28800, dst=0) -> -1199160000 -> 1932-01-01 12:00:00 (off=-28800, dst=0) 1934-01-01 12:00:00 (off=0, dst=1) -> 1934-01-01 11:00:00 (off=-28800, dst=0) -> -1136005200 -> 1934-01-01 11:00:00 (off=-28800, dst=0) 

See that for many years 2013, 1927, 1934, the hour has changed, and dst is set to 0. But in 1929 and 1932 the hour has not changed, but there is dst.

What is super strange is that tzinfo has nothing about this time range - zdump for Los Angeles shows that the closest changes occurred in 1919 and 1942.

This is in CentOS, kernel 2.6.32-358.11.1.el6.x86_64, glibc-2.12-1.107.el6.x86_64.

Further research seems to work as expected (sequentially) on MacOSX. So it looks like an error in mktime () for me, but maybe I missed something.

The test program below and is also available here.

 #include <time.h> #include <stdio.h> #include <string.h> #include <stdlib.h> char* printtm(struct tm tm) { static char buf[100]; sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d (off=%ld, dst=%d)", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_gmtoff, tm.tm_isdst); return buf; } void test(int y, int m, int d, int hh, int mm, int ss, int isdst) { // Prepare tm structs struct tm tm, tm2; memset(&tm, 0, sizeof(tm)); memset(&tm2, 0, sizeof(tm)); tm.tm_year = y - 1900; tm.tm_mon = m - 1; tm.tm_mday = d; tm.tm_hour = hh; tm.tm_min = mm; tm.tm_sec = ss; tm.tm_isdst = isdst; // Convert tm -> t -> tm and print printf("%s -> ", printtm(tm)); time_t t = mktime(&tm); printf("%s -> ", printtm(tm)); printf("%12ld -> ", t); localtime_r(&t, &tm2); printf("%s\n", printtm(tm)); } int main() { setenv("TZ", ":America/Los_Angeles", 1); tzset(); test(2013,07,01, 12,0,0, 1); test(2013,01,01, 12,0,0, 1); test(1927,01,01, 12,0,0, 1); test(1929,01,01, 12,0,0, 1); test(1932,01,01, 12,0,0, 1); test(1934,01,01, 12,0,0, 1); return 0; } 
+6
source share
1 answer

In the source for glibc mktime you can find this:

 /* tm.tm_isdst has the wrong value. Look for a neighboring time with the right value, and use its UTC offset. Heuristic: probe the adjacent timestamps in both directions, looking for the desired isdst. This should work for all real time zone histories in the tz database. */ /* Distance between probes when looking for a DST boundary. In tzdata2003a, the shortest period of DST is 601200 seconds (eg, America/Recife starting 2000-10-08 01:00), and the shortest period of non-DST surrounded by DST is 694800 seconds (Africa/Tunis starting 1943-04-17 01:00). Use the minimum of these two values, so we don't miss these short periods when probing. */ int stride = 601200; /* The longest period of DST in tzdata2003a is 536454000 seconds (eg, America/Jujuy starting 1946-10-01 01:00). The longest period of non-DST is much longer, but it makes no real sense to search for more than a year of non-DST, so use the DST max. */ int duration_max = 536454000; /* Search in both directions, so the maximum distance is half the duration; add the stride to avoid off-by-1 problems. */ int delta_bound = duration_max / 2 + stride; 

If you do the math, you will find that delta_bound is 268828200 seconds, which is about 8 and a half years. This is almost exactly the difference between the dates of zdump (in 1919 and 1942) and the times of your mysterious rearrangements (in 1928 and 1933). Each of them turns off exactly after 25 hours 30 minutes. I did not look deeper to find the reason for this magic number.

Not understanding all this, I think the comment basically means that when you are in the middle of this long non-DST period from 1919 to 1942, the algorithm tries to find a valid timestamp dst = 1 next to the one you provided erroneous dst = 1 and surrendered before he finds it, because it makes no real sense . Years outside this middle section may also not make much sense, but they behave differently as a side effect of this Jujuy-related setting.

+2
source

Source: https://habr.com/ru/post/959434/


All Articles