[17/59] Don’t mishandle years < 1970 in compute_change

Message ID 20250105055750.1668721-18-eggert@cs.ucla.edu (mailing list archive)
State New
Headers
Series time: sync mktime from Gnulib |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Paul Eggert Jan. 5, 2025, 5:56 a.m. UTC
  * time/tst-posixtz.c (tests): Test 1966 too.
* time/tzset.c (tz_rule): Change computed_for member from int
to long long int, so that we needn't worry about int overflow
when adding 1900 to a year.  All uses changed.
* time/tzset.c (compute_change): Year arg is now long long int,
not int.  All uses changed.  Do not mess up if the year is
less than 1970.
---
 time/tst-posixtz.c | 13 +++++++++--
 time/tzset.c       | 54 ++++++++++++++++++++++++++--------------------
 2 files changed, 42 insertions(+), 25 deletions(-)
  

Patch

diff --git a/time/tst-posixtz.c b/time/tst-posixtz.c
index 936c5b8fc6..482bd4f2bc 100644
--- a/time/tst-posixtz.c
+++ b/time/tst-posixtz.c
@@ -29,8 +29,17 @@  struct
   { 919973892L, "EST+5EDT,M4.1.0/2,M10.5.0/2",
     "1999/02/25 15:18:12 dst=0 zone=EST" },
 
-  /* Test Atlantic Standard / Atlantic Daylight transitions in 2024 and 2038,
-     with explicit DST rule.  */
+  /* Test Atlantic Standard / Atlantic Daylight transitions in 1966,
+     2024 and 2038, with explicit DST rule.  See Bug#32395 for why
+     1966 is being tested.  */
+  { -120074401, "AST4ADT,M3.2.0,M11.1.0",
+    "1966/03/13 01:59:59 dst=0 zone=AST" },
+  { -120074400, "AST4ADT,M3.2.0/2,M11.1.0/2",
+    "1966/03/13 03:00:00 dst=1 zone=ADT" },
+  { -99514801, "AST4ADT,M3.2.0/02:00,M11.1.0/02:00",
+    "1966/11/06 01:59:59 dst=1 zone=ADT" },
+  { -99514800, "AST4ADT,M3.2.0/02:00:00,M11.1.0/02:00:00",
+    "1966/11/06 01:00:00 dst=0 zone=AST" },
   { 1710050399, "AST4ADT,M3.2.0,M11.1.0",
     "2024/03/10 01:59:59 dst=0 zone=AST" },
   { 1710050400, "AST4ADT,M3.2.0/2,M11.1.0/2",
diff --git a/time/tzset.c b/time/tzset.c
index e869fbf3fd..7cc675334a 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -56,15 +56,15 @@  typedef struct
     /* We cache the computed time of change for a
        given year so we don't have to recompute it.  */
     __time64_t change;	/* When to change to this zone.  */
-    int computed_for;	/* Year that CHANGE is computed for.
-			   If INT_MIN, CHANGE is unspecified.  */
+    long long int computed_for;	/* Year that CHANGE is computed for.
+				   If LLONG_MIN, CHANGE is unspecified.  */
   } tz_rule;
 
 /* tz_rules[0] is standard, tz_rules[1] is daylight.  */
 static tz_rule tz_rules[2];
 
 
-static void compute_change (tz_rule *rule, int year) __THROW;
+static void compute_change (tz_rule *rule, long long int year) __THROW;
 static void tzset_internal (int always);
 
 /* List of buffers containing time zone strings. */
@@ -341,7 +341,7 @@  parse_rule (const char **tzp, int whichrule)
     secs = 2 * 60 * 60;
 
   tzr->secs = secs;
-  tzr->computed_for = INT_MIN;
+  tzr->computed_for = LLONG_MIN;
   *tzp = tz;
   return true;
 }
@@ -442,7 +442,7 @@  tzset_internal (int always)
    when the change described by RULE will occur and
    put it in RULE->change, saving YEAR in RULE->computed_for.  */
 static void
-compute_change (tz_rule *rule, int year)
+compute_change (tz_rule *rule, long long int year)
 {
   __time64_t t;
 
@@ -450,17 +450,15 @@  compute_change (tz_rule *rule, int year)
     return;
 
   /* First set T to January 1st, 0:00:00 GMT in YEAR.  */
-  if (year > 1970)
-    t = ((year - 1970) * 365
-	 + /* Compute the number of leapdays between 1970 and YEAR
-	      (exclusive).  There is a leapday every 4th year ...  */
-	 + ((year - 1) / 4 - 1970 / 4)
-	 /* ... except every 100th year ... */
-	 - ((year - 1) / 100 - 1970 / 100)
-	 /* ... but still every 400th year.  */
-	 + ((year - 1) / 400 - 1970 / 400)) * SECSPERDAY;
-  else
-    t = 0;
+  t = (((year - 1970) * 365
+	/* Compute the number of leap days between 1970 and YEAR (exclusive).
+	   There is a leap day every 4th year ...  */
+	+ (((year - 1) >> 2) - 1970 / 4)
+	/* ... except every 100th year ... */
+	- ((year - 1) / 100 - ((year - 1) % 100 < 0) - 1970 / 100)
+	/* ... but still every 400th year.  */
+	+ ((year - 1) / 400 - ((year - 1) % 400 < 0) - 1970 / 400))
+       * SECSPERDAY);
 
   switch (rule->type)
     {
@@ -484,7 +482,7 @@  compute_change (tz_rule *rule, int year)
       /* Mm.n.d - Nth "Dth day" of month M.  */
       {
 	unsigned int i;
-	int d, m1, yy0, yy1, yy2, dow;
+	int d, m1;
 	const unsigned short int *myday =
 	  &__mon_yday[__isleap (year)][rule->m];
 
@@ -493,10 +491,12 @@  compute_change (tz_rule *rule, int year)
 
 	/* Use Zeller's Congruence to get day-of-week of first day of month. */
 	m1 = (rule->m + 9) % 12 + 1;
-	yy0 = (rule->m <= 2) ? (year - 1) : year;
-	yy1 = yy0 / 100;
-	yy2 = yy0 % 100;
-	dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
+	long long int yy0 = year - (rule->m <= 2);
+	int yy1 = yy0 / 100 - (yy0 % 100 < 0);
+	int yy2 = yy0 % 100 + (yy0 % 100 < 0 ? 100 : 0);
+	int dow = (((13 * m1 - 1) / 5 + 1 + yy2 + (yy2 >> 2)
+		    + (yy1 >> 2) - 2 * yy1)
+		   % 7);
 	if (dow < 0)
 	  dow += 7;
 
@@ -530,8 +530,11 @@  compute_change (tz_rule *rule, int year)
 void
 __tz_compute (__time64_t timer, struct tm *tm)
 {
-  compute_change (&tz_rules[0], 1900 + tm->tm_year);
-  compute_change (&tz_rules[1], 1900 + tm->tm_year);
+  _Static_assert (INT_MAX <= LLONG_MAX - 1900,
+		  "long long int must be wider than int");
+  long long int year = 1900LL + tm->tm_year;
+  compute_change (&tz_rules[0], year);
+  compute_change (&tz_rules[1], year);
 
   /* Distinguish between northern and southern hemisphere.
      For the latter the daylight saving time ends in the next year.  */
@@ -574,6 +577,11 @@  weak_alias (__tzset, tzset)
 struct tm *
 __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
 {
+  /* Check that __time64_t is wider than int,
+     so that __tz_convert cannot fail due to integer overflow.  */
+  _Static_assert (sizeof (int) < sizeof (__time64_t),
+		  "__time64_t must be wider than int");
+
   int leap_correction;
   bool leap_extra_sec;