* lib/mktime.c (__mktime_internal): When tm_isdst disagrees with
what was requested, search at most a year (+ stride) from the
requested time for a matching tm_isdst, and ignore the request if
the search fails.  This is more likely to match user expectations
in timezones like Asia/Kolkata.
Problem reported by Florian Weimer in:
https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html
---
 ChangeLog    |  9 +++++++++
 lib/mktime.c | 38 ++++++++++++--------------------------
 2 files changed, 21 insertions(+), 26 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b3c6e48e6d..629f1b8d3f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2025-01-04  Paul Eggert  <egg...@cs.ucla.edu>
 
+       mktime: improve tm_isdst heuristic
+       * lib/mktime.c (__mktime_internal): When tm_isdst disagrees with
+       what was requested, search at most a year (+ stride) from the
+       requested time for a matching tm_isdst, and ignore the request if
+       the search fails.  This is more likely to match user expectations
+       in timezones like Asia/Kolkata.
+       Problem reported by Florian Weimer in:
+       https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html
+
        mktime: support glibc locking
        This is part of my attempt to merge glibc and gnulib mktime.c.
        It should not affect Gnulib-using code.
diff --git a/lib/mktime.c b/lib/mktime.c
index 99f014c6bd..4448a92c06 100644
--- a/lib/mktime.c
+++ b/lib/mktime.c
@@ -355,9 +355,7 @@ __mktime_internal (struct tm *tp, bool local, 
mktime_offset_t *offset)
   int mday = tp->tm_mday;
   int mon = tp->tm_mon;
   int year_requested = tp->tm_year;
-
-  /* Ignore any tm_isdst request for timegm.  */
-  int isdst = local ? tp->tm_isdst : 0;
+  int isdst = tp->tm_isdst;
 
   /* True if the previous probe was DST.  */
   bool dst2 = false;
@@ -449,13 +447,10 @@ __mktime_internal (struct tm *tp, bool local, 
mktime_offset_t *offset)
 
         Heuristic: probe the adjacent timestamps in both directions,
         looking for the desired isdst.  If none is found within a
-        reasonable duration bound, assume a one-hour DST difference.
+        reasonable duration bound, ignore the disagreement.
         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
         (e.g., America/Recife starting 2000-10-08 01:00), and the
@@ -465,21 +460,17 @@ __mktime_internal (struct tm *tp, bool local, 
mktime_offset_t *offset)
         periods when probing.  */
       int stride = 601200;
 
-      /* 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.  */
-      int delta_bound = duration_max / 2 + stride;
+      /* Do not probe too far away from the requested time,
+         by striding until at least a year has passed, but then giving up.
+         This helps avoid unexpected results in (for example) Asia/Kolkata,
+         for which today's users expect to see no DST even though it
+         did observe DST long ago.  */
+      int year_seconds_bound = 366 * 24 * 60 * 60 + 1;
+      int delta_bound = year_seconds_bound + stride;
 
       int delta, direction;
 
+      /* Search in both directions, closest first.  */
       for (delta = stride; delta < delta_bound; delta += stride)
        for (direction = -1; direction <= 1; direction += 2)
          {
@@ -509,13 +500,8 @@ __mktime_internal (struct tm *tp, bool local, 
mktime_offset_t *offset)
              }
          }
 
-      /* No unusual DST offset was found nearby.  Assume one-hour DST.  */
-      t += 60 * 60 * dst_difference;
-      if (mktime_min <= t && t <= mktime_max && __tz_convert (t, local, &tm))
-       goto offset_found;
-
-      __set_errno (EOVERFLOW);
-      return -1;
+      /* No probe with the requested tm_isdst was found nearby.
+         Ignore the requested tm_isdst.  */
     }
 
  offset_found:
-- 
2.45.2


Reply via email to