summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2022-02-19 15:04:43 -0800
committerPaul Eggert <eggert@cs.ucla.edu>2022-02-19 15:28:11 -0800
commit06b2e943be39284783ff81ac6c9503200f41dba3 (patch)
tree05f59fa34157a79ebaef39a3433838da9b7d5540
parent9f48fb992a3d7e96610c4ce8be969cff2d61a01b (diff)
downloadgnulib-06b2e943be39284783ff81ac6c9503200f41dba3.tar.gz
mktime: improve heuristic for ca-1986 Indiana DST
Problem reported by Mark Krenz <https://bugs.gnu.org/48085>. * 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.
-rw-r--r--ChangeLog17
-rw-r--r--lib/mktime.c28
-rw-r--r--m4/mktime.m429
-rw-r--r--tests/test-parse-datetime.c21
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 <eggert@cs.ucla.edu>
+
+ mktime: improve heuristic for ca-1986 Indiana DST
+ Problem reported by Mark Krenz <https://bugs.gnu.org/48085>.
+ * 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 <eggert@cs.ucla.edu>
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
<https://bugs.gnu.org/12206>. */
- 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 */