]> Savannah Git Hosting - gnulib.git/commitdiff
mktime: improve heuristic for ca-1986 Indiana DST
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 19 Feb 2022 23:04:43 +0000 (15:04 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 19 Feb 2022 23:28:11 +0000 (15:28 -0800)
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.

ChangeLog
lib/mktime.c
m4/mktime.m4
tests/test-parse-datetime.c

index 4bf0cec7f066012eba2cb1af671a5c9992c2a3e4..4d56be83d4745d3d6cdaf9f236e6e59f9b103180 100644 (file)
--- 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
index aa12e28e1681db151cd691043ffd57fe099e5ed6..7dc9d67ef9d52ce5559180ea31205254fefdff9f 100644 (file)
@@ -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;
     }
index d48f40d187bd3a1b521f854820e775f84f852fbc..431b17dcb0dc1f0ba1d737a6c1bf339d69ba9689 100644 (file)
@@ -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;
 }]])],
index 1e7955bc96a0ab9745982b191cc2d27163011594..4310ee8a3dfbefeb8a3287ab35864961054092be 100644 (file)
@@ -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 */