[tz] [PROPOSED] Fix ambiguous leapsecs by rolling up to a minute

Paul Eggert eggert at cs.ucla.edu
Sun Sep 12 00:36:06 UTC 2021


This fixes a longstanding ambiguity in the output of localtime in
oddball timezones where a positive leap second occurs when the UTC
offset is not a multiple of 60 seconds.  See NEWS for details.
* NEWS: Describe fix.
* localtime.c (timesub): Use "??:??:60" at the end of the
localtime minute containing the leap second, even when the second
marked "??:??:60" is not itself a leap second.  This avoids
ambiguity in localtime output, at the cost of having the localtime
leap second occur up to a minute away from the UTC leap second
when the UTC offset is not a multiple of 60 seconds.
* tzfile.5: Document the fixed and unfixed behaviors.
---
 NEWS        | 18 ++++++++++++++++++
 localtime.c | 22 +++++++++++++---------
 tzfile.5    | 12 ++++++++++++
 3 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/NEWS b/NEWS
index 1bf9cf3..6a77932 100644
--- a/NEWS
+++ b/NEWS
@@ -109,6 +109,24 @@ Unreleased, experimental changes
     transition in the table, or when handling far-future timestamps
     in slim TZif files lacking leap seconds.
 
+    Fix localtime misbehavior involving positive leap seconds in a
+    timezone with a UTC offset that is not on a minute boundary.
+    (No such timezone exists in tzdb, luckily.)  Without the fix,
+    the timestamp was ambiguous during a positive leap second.
+    With the fix, the localtime leap second appears at the end of the
+    localtime minute containing the UTC second just before the UTC
+    leap second, which means leapseconds roll slightly in these
+    oddball timezones.  Here is how the fix affects timestamps in a
+    timezone with UTC offset +01:23:45 and with a positive leap second
+    78796801 (1972-06-30 23:59:60 UTC):
+
+	time_t    without the fix      with the fix
+	78796800  1972-07-01 01:23:45  1972-07-01 01:23:45
+	78796801  1972-07-01 01:23:45  1972-07-01 01:23:46
+	...
+	78796815  1972-07-01 01:23:59  1972-07-01 01:23:60
+	78796816  1972-07-01 01:24:00  1972-07-01 01:24:00
+
     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 88fd35d..6a687d7 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1681,20 +1681,23 @@ timesub(const time_t *timep, int_fast32_t offset,
 	register time_t			tdays;
 	register const int *		ip;
 	register int_fast32_t		corr;
-	register bool			hit;
 	register int			i;
 	int_fast32_t idays, rem, dayoff, dayrem;
 	time_t y;
 
+	/* If less than SECSPERMIN, the number of seconds since the
+	   most recent positive leap second; otherwise, do not add 1
+	   to localtime tm_sec because of leap seconds.  */
+	time_t secs_since_posleap = SECSPERMIN;
+
 	corr = 0;
-	hit = false;
 	i = (sp == NULL) ? 0 : sp->leapcnt;
 	while (--i >= 0) {
 		lp = &sp->lsis[i];
 		if (*timep >= lp->ls_trans) {
 			corr = lp->ls_corr;
-			hit = (*timep == lp->ls_trans
-			       && (i == 0 ? 0 : lp[-1].ls_corr) < corr);
+			if ((i == 0 ? 0 : lp[-1].ls_corr) < corr)
+			  secs_since_posleap = *timep - lp->ls_trans;
 			break;
 		}
 	}
@@ -1761,11 +1764,12 @@ timesub(const time_t *timep, int_fast32_t offset,
 	tmp->tm_hour = rem / SECSPERHOUR;
 	rem %= SECSPERHOUR;
 	tmp->tm_min = rem / SECSPERMIN;
-	/*
-	** A positive leap second requires a special
-	** representation. This uses "... ??:59:60" et seq.
-	*/
-	tmp->tm_sec = rem % SECSPERMIN + hit;
+	tmp->tm_sec = rem % SECSPERMIN;
+
+	/* Use "... ??:??:60" at the end of the localtime minute containing
+	   the second just before the positive leap second.  */
+	tmp->tm_sec += secs_since_posleap <= tmp->tm_sec;
+
 	ip = mon_lengths[isleap(y)];
 	for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
 		idays -= ip[tmp->tm_mon];
diff --git a/tzfile.5 b/tzfile.5
index ec78689..4a88b75 100644
--- a/tzfile.5
+++ b/tzfile.5
@@ -279,6 +279,10 @@ the purpose of skipping over them.
 Readers should calculate the total lengths of the
 headers and data blocks and check that they all fit within
 the actual file size, as part of a validity check for the file.
+.PP
+When a positive leap second occurs, readers should append a 60th
+second to the local minute containing the second just before the leap
+second.
 .SS Common interoperability issues
 This section documents common problems in reading or writing TZif files.
 Most of these are problems in generating TZif files for use by
@@ -398,6 +402,14 @@ thus swapping standard and daylight saving time.
 Although this workaround misidentifies which part of the year
 uses daylight saving time, it records UT offsets and time zone
 abbreviations correctly.
+.IP *
+Some readers generate ambiguous timestamps for positive leap seconds
+that occur when the UTC offset is not a multiple of 60 seconds.
+For example, in a timezone with UTC offset +01:23:45 and with
+a positive leap second 78796801 (1972-06-30 23:59:60 UTC), they
+map both 78796800 and 78796801 to 01:23:45 local time the next day
+instead of mapping the latter to 01:23:46, and they map 78796815 to
+01:23:59 instead of to 01:23:60.
 .PP
 Some interoperability problems are reader bugs that
 are listed here mostly as warnings to developers of readers.
-- 
2.31.1



More information about the tz mailing list