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