From 06b2e943be39284783ff81ac6c9503200f41dba3 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 19 Feb 2022 15:04:43 -0800 Subject: [PATCH] mktime: improve heuristic for ca-1986 Indiana DST Problem reported by Mark Krenz . * lib/mktime.c (__mktime_internal): Be more generous about accepting arguments with the wrong value of tm_isdst, by falling back to a one-hour DST difference if we find no nearby DST that is unusual. This fixes a problem where "1986-04-28 00:00 EDT" was rejected when TZ="America/Indianapolis" because the nearest DST timestamp occurred in 1970, a temporal distance too great for the old heuristic. This also also narrows the search a bit, which is a minor performance win. * m4/mktime.m4 (gl_FUNC_MKTIME_WORKS): Check for putenv failures and for Bug#48085. * tests/test-parse-datetime.c (main): Test for setenv failures and for Bug#48085. --- ChangeLog | 17 +++++++++++++++++ lib/mktime.c | 28 ++++++++++++++++++++-------- m4/mktime.m4 | 29 +++++++++++++++++++++++++---- tests/test-parse-datetime.c | 21 +++++++++++++++++++-- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4bf0cec7f0..4d56be83d4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2022-02-19 Paul Eggert + + mktime: improve heuristic for ca-1986 Indiana DST + Problem reported by Mark Krenz . + * lib/mktime.c (__mktime_internal): Be more generous about + accepting arguments with the wrong value of tm_isdst, by falling + back to a one-hour DST difference if we find no nearby DST that is + unusual. This fixes a problem where "1986-04-28 00:00 EDT" was + rejected when TZ="America/Indianapolis" because the nearest DST + timestamp occurred in 1970, a temporal distance too great for the + old heuristic. This also also narrows the search a bit, which + is a minor performance win. + * m4/mktime.m4 (gl_FUNC_MKTIME_WORKS): + Check for putenv failures and for Bug#48085. + * tests/test-parse-datetime.c (main): + Test for setenv failures and for Bug#48085. + 2022-02-12 Paul Eggert filevercmp: fix several unexpected results diff --git a/lib/mktime.c b/lib/mktime.c index aa12e28e16..7dc9d67ef9 100644 --- a/lib/mktime.c +++ b/lib/mktime.c @@ -429,8 +429,13 @@ __mktime_internal (struct tm *tp, 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. */ + looking for the desired isdst. If none is found within a + reasonable duration bound, assume a one-hour DST difference. + This should work for all real time zone histories in the tz + database. */ + + /* +1 if we wanted standard time but got DST, -1 if the reverse. */ + int dst_difference = (isdst == 0) - (tm.tm_isdst == 0); /* Distance between probes when looking for a DST boundary. In tzdata2003a, the shortest period of DST is 601200 seconds @@ -441,12 +446,14 @@ __mktime_internal (struct tm *tp, periods when probing. */ int stride = 601200; - /* The longest period of DST in tzdata2003a is 536454000 seconds - (e.g., 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; + /* In TZDB 2021e, the longest period of DST (or of non-DST), in + which the DST (or adjacent DST) difference is not one hour, + is 457243209 seconds: e.g., America/Cambridge_Bay with leap + seconds, starting 1965-10-31 00:00 in a switch from + double-daylight time (-05) to standard time (-07), and + continuing to 1980-04-27 02:00 in a switch from standard time + (-07) to daylight time (-06). */ + int duration_max = 457243209; /* Search in both directions, so the maximum distance is half the duration; add the stride to avoid off-by-1 problems. */ @@ -483,6 +490,11 @@ __mktime_internal (struct tm *tp, } } + /* No unusual DST offset was found nearby. Assume one-hour DST. */ + t += 60 * 60 * dst_difference; + if (mktime_min <= t && t <= mktime_max && convert_time (convert, t, &tm)) + goto offset_found; + __set_errno (EOVERFLOW); return -1; } diff --git a/m4/mktime.m4 b/m4/mktime.m4 index d48f40d187..431b17dcb0 100644 --- a/m4/mktime.m4 +++ b/m4/mktime.m4 @@ -1,4 +1,4 @@ -# serial 36 +# serial 37 dnl Copyright (C) 2002-2003, 2005-2007, 2009-2022 Free Software Foundation, dnl Inc. dnl This file is free software; the Free Software Foundation @@ -82,7 +82,8 @@ spring_forward_gap () instead of "TZ=America/Vancouver" in order to detect the bug even on systems that don't support the Olson extension, or don't have the full zoneinfo tables installed. */ - putenv ("TZ=PST8PDT,M4.1.0,M10.5.0"); + if (putenv ("TZ=PST8PDT,M4.1.0,M10.5.0") != 0) + return -1; tm.tm_year = 98; tm.tm_mon = 3; @@ -170,7 +171,8 @@ year_2050_test () instead of "TZ=America/Vancouver" in order to detect the bug even on systems that don't support the Olson extension, or don't have the full zoneinfo tables installed. */ - putenv ("TZ=PST8PDT,M4.1.0,M10.5.0"); + if (putenv ("TZ=PST8PDT,M4.1.0,M10.5.0") != 0) + return -1; t = mktime (&tm); @@ -181,6 +183,25 @@ year_2050_test () || (0 < t && answer - 120 <= t && t <= answer + 120)); } +static int +indiana_test () +{ + if (putenv ("TZ=America/Indiana/Indianapolis") != 0) + return -1; + struct tm tm; + tm.tm_year = 1986 - 1900; tm.tm_mon = 4 - 1; tm.tm_mday = 28; + tm.tm_hour = 16; tm.tm_min = 24; tm.tm_sec = 50; tm.tm_isdst = 0; + time_t std = mktime (&tm); + if (! (std == 515107490 || std == 515107503)) + return 1; + + /* This platform supports TZDB, either without or with leap seconds. + Return true if GNU Bug#48085 is absent. */ + tm.tm_isdst = 1; + time_t dst = mktime (&tm); + return std - dst == 60 * 60; +} + int main () { @@ -236,7 +257,7 @@ main () result |= 16; if (! spring_forward_gap ()) result |= 32; - if (! year_2050_test ()) + if (! year_2050_test () || ! indiana_test ()) result |= 64; return result; }]])], diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c index 1e7955bc96..4310ee8a3d 100644 --- a/tests/test-parse-datetime.c +++ b/tests/test-parse-datetime.c @@ -126,7 +126,7 @@ main (_GL_UNUSED int argc, char **argv) should disable any leap second support. Otherwise, there will be a problem with glibc on sites that default to leap seconds; see . */ - setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1); + ASSERT (setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1) == 0); gmtoff = gmt_offset (ref_time); @@ -375,8 +375,25 @@ main (_GL_UNUSED int argc, char **argv) ASSERT (result.tv_sec == result2.tv_sec && result.tv_nsec == result2.tv_nsec); + /* If this platform has TZDB, check for GNU Bug#48085. */ + ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0); + now.tv_sec = 1619641490; + now.tv_nsec = 0; + struct tm *tm = localtime (&now.tv_sec); + if (tm && tm->tm_year == 2021 - 1900 && tm->tm_mon == 4 - 1 + && tm->tm_mday == 28 && tm->tm_hour == 16 && tm->tm_min == 24 + && 0 < tm->tm_isdst) + { + int has_leap_seconds = tm->tm_sec != now.tv_sec % 60; + p = "now - 35 years"; + ASSERT (parse_datetime (&result, p, &now)); + LOG (p, now, result); + ASSERT (result.tv_sec + == 515107490 - 60 * 60 + (has_leap_seconds ? 13 : 0)); + } + /* Check that some "next Monday", "last Wednesday", etc. are correct. */ - setenv ("TZ", "UTC0", 1); + ASSERT (setenv ("TZ", "UTC0", 1) == 0); for (i = 0; day_table[i]; i++) { unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */ -- 2.32.0