[tz] [PATCH 6/7] Fix localtime bug with slim TZif files with leaps

Paul Eggert eggert at cs.ucla.edu
Wed Mar 17 01:46:04 UTC 2021


* NEWS: Mention this.
* localtime.c (tzparse): New arg PARSELB specifying a lower
bound for the desired transitions.  Use this so that extending a
table by interpreting a TZif file’s TZ string will go for the
needed 400 years from the last explicit transition.  All
callers changed.
---
 NEWS        |  5 +++++
 localtime.c | 45 ++++++++++++++++++++++++++++++++++-----------
 2 files changed, 39 insertions(+), 11 deletions(-)

diff --git a/NEWS b/NEWS
index 2e6b443..559e96e 100644
--- a/NEWS
+++ b/NEWS
@@ -66,6 +66,11 @@ Unreleased, experimental changes
     set to a POSIX-conforming but unusual TZ string like
     "EST5EDT4,0/0,J365/0", where almost all the year is DST.
 
+    Fix yet another bug that caused 'localtime' etc. to mishandle slim
+    TZif files containing leap seconds after the last explicit
+    transition in the table, or when handling far-future timestamps
+    in slim TZif files lacking leap seconds.
+
     Fix an unlikely bug that caused 'localtime' etc. to misbehave if
     civil time changes a few seconds before time_t wraps around, when
     leap seconds are enabled.
diff --git a/localtime.c b/localtime.c
index aab806a..a18d130 100644
--- a/localtime.c
+++ b/localtime.c
@@ -155,7 +155,7 @@ static bool normalize_overflow32(int_fast32_t *, int *, int);
 static struct tm *timesub(time_t const *, int_fast32_t, struct state const *,
 			  struct tm *);
 static bool typesequiv(struct state const *, int, int);
-static bool tzparse(char const *, struct state *);
+static bool tzparse(char const *, struct state *, time_t);
 
 #ifdef ALL_STATE
 static struct state *	lclptr;
@@ -596,9 +596,15 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 		up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
 		sp->typecnt + 2 <= TZ_MAX_TYPES) {
 			struct state	*ts = &lsp->u.st;
+			time_t parselb = TIME_T_MIN;
+			if (0 < sp->timecnt)
+			  parselb = sp->ats[sp->timecnt - 1];
+			if (0 < sp->leapcnt
+			    && parselb < sp->lsis[sp->leapcnt - 1].ls_trans)
+			  parselb = sp->lsis[sp->leapcnt - 1].ls_trans;
 
 			up->buf[nread - 1] = '\0';
-			if (tzparse(&up->buf[1], ts)) {
+			if (tzparse(&up->buf[1], ts, parselb)) {
 
 			  /* Attempt to reuse existing abbreviations.
 			     Without this, America/Anchorage would be right on
@@ -1064,7 +1070,7 @@ transtime(const int year, register const struct rule *const rulep,
 */
 
 static bool
-tzparse(const char *name, struct state *sp)
+tzparse(const char *name, struct state *sp, time_t parselb)
 {
 	const char *			stdname;
 	const char *			dstname;
@@ -1129,11 +1135,10 @@ tzparse(const char *name, struct state *sp)
 			struct rule	start;
 			struct rule	end;
 			register int	year;
-			register int	yearlim;
 			register int	timecnt;
 			time_t		janfirst;
 			int_fast32_t janoffset = 0;
-			int yearbeg;
+			int yearbeg, yearlim;
 
 			++name;
 			if ((name = getrule(name, &start)) == NULL)
@@ -1163,9 +1168,25 @@ tzparse(const char *name, struct state *sp)
 			    janoffset = -yearsecs;
 			    break;
 			  }
-			} while (EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+			} while (parselb < janfirst
+				 && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
 
-			yearlim = yearbeg + YEARSPERREPEAT + 1;
+			while (true) {
+			  int_fast32_t yearsecs
+			    = year_lengths[isleap(yearbeg)] * SECSPERDAY;
+			  int yearbeg1 = yearbeg;
+			  time_t janfirst1 = janfirst;
+			  if (increment_overflow_time(&janfirst1, yearsecs)
+			      || increment_overflow(&yearbeg1, 1)
+			      || parselb <= janfirst1)
+			    break;
+			  yearbeg = yearbeg1;
+			  janfirst = janfirst1;
+			}
+
+			yearlim = yearbeg;
+			if (increment_overflow(&yearlim, YEARSPERREPEAT + 1))
+			  yearlim = INT_MAX;
 			for (year = yearbeg; year < yearlim; year++) {
 				int_fast32_t
 				  starttime = transtime(year, &start, stdoffset),
@@ -1187,12 +1208,14 @@ tzparse(const char *name, struct state *sp)
 					sp->ats[timecnt] = janfirst;
 					if (! increment_overflow_time
 					    (&sp->ats[timecnt],
-					     janoffset + starttime))
+					     janoffset + starttime)
+					    && parselb <= sp->ats[timecnt])
 					  sp->types[timecnt++] = !reversed;
 					sp->ats[timecnt] = janfirst;
 					if (! increment_overflow_time
 					    (&sp->ats[timecnt],
-					     janoffset + endtime)) {
+					     janoffset + endtime)
+					    && parselb <= sp->ats[timecnt]) {
 					  sp->types[timecnt++] = reversed;
 					}
 				}
@@ -1312,7 +1335,7 @@ static void
 gmtload(struct state *const sp)
 {
 	if (tzload(gmt, sp, true) != 0)
-	  tzparse("GMT0", sp);
+	  tzparse("GMT0", sp, TIME_T_MIN);
 }
 
 /* Initialize *SP to a value appropriate for the TZ setting NAME.
@@ -1335,7 +1358,7 @@ zoneinit(struct state *sp, char const *name)
     return 0;
   } else {
     int err = tzload(name, sp, true);
-    if (err != 0 && name && name[0] != ':' && tzparse(name, sp))
+    if (err != 0 && name && name[0] != ':' && tzparse(name, sp, TIME_T_MIN))
       err = 0;
     if (err == 0)
       scrub_abbrs(sp);
-- 
2.27.0



More information about the tz mailing list