[PROPOSED 1/3] Fix leap second bug after last explicit transition

The client code mishandled timestamps after the last explicit transition in a TZif file that had both leap seconds and daylight saving time transitions projected into the indefinite future. When interpreting the TZ string embedded at the end of the TZif file, the code neglected to adjust the corresponding DST transitions for leap seconds, which meant the DST transitions were calculated to occur too early. For example, with TZ set to '.../zoneinfo-leaps/America/Los_Angeles', zdump reported a DST transition on 2038-03-14 from 01:59:32.999... to 02:59:33 instead of the correct transition from 01:59:59.999... to 03:00:00. * NEWS: Mention this. * localtime.c (tzloadbody): Apply leap second corrections to transitions derived from the trailing TZ string in a TZif file. (leapcorr) [!STD_INSPIRED]: Define in this case too, since tzloadbody now uses it. --- NEWS | 8 ++++++++ localtime.c | 35 +++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index 961aa08..72e5c04 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,14 @@ Unreleased, experimental changes Changes to code + localtime.c no longer mishandles timestamps after the last + transition in a TZif file with leap seconds and with daylight + saving time transitions projected into the indefinite future. + For example, with TZ='America/Los_Angeles' with leap seconds, + zdump formerly reported a DST transition on 2038-03-14 + from 01:59:32.999... to 02:59:33 instead of the correct transition + from 01:59:59.999... to 03:00:00. + The configuration macros HAVE_TZNAME and USG_COMPAT should now be set to 1 if the system library supports the feature, and 2 if not. As before, these macros are nonzero if tzcode should support the diff --git a/localtime.c b/localtime.c index d3f8fc7..6623eac 100644 --- a/localtime.c +++ b/localtime.c @@ -158,6 +158,7 @@ static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, struct tm *); static bool increment_overflow(int *, int); static bool increment_overflow_time(time_t *, int_fast32_t); +static int_fast64_t leapcorr(struct state const *, time_t); static bool normalize_overflow32(int_fast32_t *, int *, int); static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, struct tm *); @@ -641,11 +642,13 @@ tzloadbody(char const *name, struct state *sp, bool doextend, for (i = 0; i < ts->timecnt; i++) if (sp->timecnt == 0 - || sp->ats[sp->timecnt - 1] < ts->ats[i]) + || (sp->ats[sp->timecnt - 1] + < ts->ats[i] + leapcorr(sp, ts->ats[i]))) break; while (i < ts->timecnt && sp->timecnt < TZ_MAX_TIMES) { - sp->ats[sp->timecnt] = ts->ats[i]; + sp->ats[sp->timecnt] + = ts->ats[i] + leapcorr(sp, ts->ats[i]); sp->types[sp->timecnt] = (sp->typecnt + ts->types[i]); sp->timecnt++; @@ -2239,20 +2242,6 @@ timeoff(struct tm *tmp, long offset) #endif /* defined STD_INSPIRED */ -/* -** XXX--is the below the right way to conditionalize?? -*/ - -#ifdef STD_INSPIRED - -/* -** IEEE Std 1003.1 (POSIX) says that 536457599 -** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which -** is not the case if we are accounting for leap seconds. -** So, we provide the following conversion routines for use -** when exchanging timestamps with POSIX conforming systems. -*/ - static int_fast64_t leapcorr(struct state const *sp, time_t t) { @@ -2268,6 +2257,20 @@ leapcorr(struct state const *sp, time_t t) return 0; } +/* +** XXX--is the below the right way to conditionalize?? +*/ + +#ifdef STD_INSPIRED + +/* +** IEEE Std 1003.1 (POSIX) says that 536457599 +** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which +** is not the case if we are accounting for leap seconds. +** So, we provide the following conversion routines for use +** when exchanging timestamps with POSIX conforming systems. +*/ + NETBSD_INSPIRED_EXTERN time_t time2posix_z(struct state *sp, time_t t) { -- 2.24.1

* zic.c (leapadd): Simplify by having one parameter CORRECTION instead of two parameters POSITIVE and COUNT (the latter always 1). The old code was for multiple adjacent leap seconds, a misfeature that was removed from zic.c in 2017c. --- zic.c | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/zic.c b/zic.c index a84703a..8eb0af3 100644 --- a/zic.c +++ b/zic.c @@ -153,7 +153,7 @@ extern int optind; static void addtt(zic_t starttime, int type); static int addtype(zic_t, char const *, bool, bool, bool); -static void leapadd(zic_t, bool, int, int); +static void leapadd(zic_t, int, int); static void adjleap(void); static void associate(void); static void dolink(const char *, const char *, bool); @@ -1559,15 +1559,12 @@ inleap(char **fields, int nfields) tod = gethms(fields[LP_TIME], _("invalid time of day")); cp = fields[LP_CORR]; { - register bool positive; - int count; + int correction; if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ - positive = false; - count = 1; + correction = -1; } else if (strcmp(cp, "+") == 0) { - positive = true; - count = 1; + correction = 1; } else { error(_("illegal CORRECTION field on Leap line")); return; @@ -1583,7 +1580,7 @@ inleap(char **fields, int nfields) error(_("leap second precedes Epoch")); return; } - leapadd(t, positive, lp->l_value, count); + leapadd(t, correction, lp->l_value); } } @@ -2969,28 +2966,24 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut) } static void -leapadd(zic_t t, bool positive, int rolling, int count) +leapadd(zic_t t, int correction, int rolling) { - register int i, j; + register int i; - if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) { + if (TZ_MAX_LEAPS <= leapcnt) { error(_("too many leap seconds")); exit(EXIT_FAILURE); } for (i = 0; i < leapcnt; ++i) if (t <= trans[i]) break; - do { - for (j = leapcnt; j > i; --j) { - trans[j] = trans[j - 1]; - corr[j] = corr[j - 1]; - roll[j] = roll[j - 1]; - } - trans[i] = t; - corr[i] = positive ? 1 : -count; - roll[i] = rolling; - ++leapcnt; - } while (positive && --count != 0); + memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans); + memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr); + memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll); + trans[i] = t; + corr[i] = correction; + roll[i] = rolling; + ++leapcnt; } static void -- 2.24.1

On Tue 2020-01-14T18:23:02-0800 Paul Eggert hath writ:
* zic.c (leapadd): Simplify by having one parameter CORRECTION instead of two parameters POSITIVE and COUNT (the latter always 1). The old code was for multiple adjacent leap seconds, a misfeature that was removed from zic.c in 2017c.
Do not forget that the original draft of CCIR Recommendation 460 allowed for leaps of "integral multiples" of one second. The 1970 CCIR assembly documents are online at https://www.itu.int/en/history/Pages/AssemblyRadio.aspx?conf=4.278 The particulars of the change from allowing more than one leap second to only one leap second are in volume 7 on page 116 at http://search.itu.int/history/HistoryDigitalCollectionDocLibrary/4.278.43.en... and the connection between VII/1008 and Rec. 460 is on page 260. The reasons why Study Group VII recommended a text allowing multiple seconds are not evident, and neither are the reasons why Document VII/1008 was amended on the floor during the Plenary Assembly, but what the CCIR un-did in 1970 could still be re-done by ITU-R or a sucessor agency. -- Steve Allen <sla@ucolick.org> WGS-84 (GPS) UCO/Lick Observatory--ISB 260 Natural Sciences II, Room 165 Lat +36.99855 1156 High Street Voice: +1 831 459 3046 Lng -122.06015 Santa Cruz, CA 95064 https://www.ucolick.org/~sla/ Hgt +250 m

On 1/14/20 7:04 PM, Steve Allen wrote:
the original draft of CCIR Recommendation 460 allowed for leaps of "integral multiples" of one second.
Ah, I didn't know that. Still, the 1989 C standard botched this, since C89 allowed for leaps of one or two (but no more) seconds, apparently on the mistaken assumption that that could have happened under the then-current leap second rules. And this botch was copied into POSIX. Although the two-leap-second botch was fixed in the C99 and POSIX-2001 standards, it was still present (though with bugs) in tzcode until the buggy support was removed in tzcode 2017c.

On 2020-01-15 03:04, Steve Allen wrote:
The reasons why Study Group VII recommended a text allowing multiple seconds are not evident, and neither are the reasons why Document VII/1008 was amended on the floor during the Plenary Assembly, but what the CCIR un-did in 1970 could still be re-done by ITU-R or a sucessor agency.
Isn't that obvious? If UTC cannot deviate from UT1 by more than 0.7 s (as Recommendation 460 originally stipulated) then UTC cannot have leaps by more than 1.4 s. Leaps by N s are only possible when |UTC - UT1| is allowed to be >= N/2 s. The draft version of Study Group VII (the one that allows for leaps in UTC by an integral number of seconds) specifies no bound for |UTC - UT1|. Michael Deckers.

On Wed 2020-01-15T11:28:35+0000 Michael H Deckers hath writ:
Isn't that obvious? If UTC cannot deviate from UT1 by more than 0.7 s (as Recommendation 460 originally stipulated) then UTC cannot have leaps by more than 1.4 s. Leaps by N s are only possible when |UTC - UT1| is allowed to be >= N/2 s.
The draft version of Study Group VII (the one that allows for leaps in UTC by an integral number of seconds) specifies no bound for |UTC - UT1|.
The original version of CCIR Rec. 460 did not specify any bound. See volume 3 page 227 http://search.itu.int/history/HistoryDigitalCollectionDocLibrary/4.278.43.en... The draft with "integral multiples" was pushed through in a rush in 1970 January because of an imminent political deadline from Germany and other political pressure from USSR. The technical details with max 0.7 second deviation came from Report 517 which was not forged until 1971 February and included into the CCIR volume 3 as addendum starting on page 258a. That 0.7 limit was disregarded in less than two years also due to Soviet political pressure, leading to a rewrite that gave the limit of 0.8 second even. though the 0.7 limit had already been hard-coded into the format of the broadcast time signals. -- Steve Allen <sla@ucolick.org> WGS-84 (GPS) UCO/Lick Observatory--ISB 260 Natural Sciences II, Room 165 Lat +36.99855 1156 High Street Voice: +1 831 459 3046 Lng -122.06015 Santa Cruz, CA 95064 https://www.ucolick.org/~sla/ Hgt +250 m

On 2020-01-15 15:53, Steve Allen wrote:
The original version of CCIR Rec. 460 did not specify any bound. See volume 3 page 227 http://search.itu.int/history/HistoryDigitalCollectionDocLibrary/4.278.43.en...
I see - that version of Rec 460 does not even say when leap seconds may be introduced. But it references "detailed instructions" (which is Report 517) for its implementation, so I assume both documents applied to UTC since 1972-01-01. This is corroborated by Rec 374-2 (on p 222 of that volume).
The technical details with max 0.7 second deviation came from Report 517 which was not forged until 1971 February and included into the CCIR volume 3 as addendum starting on page 258a. That 0.7 limit was disregarded in less than two years also due to Soviet political pressure, leading to a rewrite that gave the limit of 0.8 second even. though the 0.7 limit had already been hard-coded into the format of the broadcast time signals.
My historical knowledge is too weak to let me understand the political ramifications. But apparently the second leap second insertion prescribed by the BIH already violated the 0.7 s bound: the difference UT1 - UTC was about +0.81 s on 1973-01-01 (after the leap). So I find it quite plausible that the bound for |UTC - UT1| was soon increased to 0.9 s (while |DUT1| is required to be <= 0.8 s) -- even without the political pressure. Michael Deckers.

* Makefile (EXPIRES_LINE): New macro. (leapseconds): Use it. * NEWS, zic.8: Mention this. * leapseconds.awk: Also output an "Expires" line, but comment it out for now so that older zic implementations do not reject the generated leapseconds file. * zic.c (LC_EXPIRES, EXPIRES_FIELDS): New macros. (leapexpires): New static var. (leap_line_codes, infile, adjleap): Add support for expiration lines. (getleapdatetime): New function, with much of the former inleap implementation. (inleap): Use it. (inexpires): New function. --- Makefile | 12 ++++- NEWS | 14 ++++++ leapseconds.awk | 11 +++++ zic.8 | 41 +++++++++++++++- zic.c | 125 +++++++++++++++++++++++++++++++++--------------- 5 files changed, 162 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 865b39f..57ee77b 100644 --- a/Makefile +++ b/Makefile @@ -150,6 +150,15 @@ TIME_T_ALTERNATIVES_TAIL = int32_t uint32_t uint64_t REDO= posix_right +# Whether to put an "Expires" line in the leapseconds file. +# Use EXPIRES_LINE=1 to put the line in, 0 to omit it. +# The EXPIRES_LINE value matters only if REDO's value contains "right". +# If you change EXPIRES_LINE, remove the leapseconds file before running "make". +# zic's support for the Expires line was introduced in tzdb 2020a, +# and EXPIRES_LINE defaults to 0 for now so that the leapseconds file +# can be given to older zic implementations. +EXPIRES_LINE= 0 + # To install data in text form that has all the information of the TZif data, # (optionally incorporating leap second information), use # TZDATA_TEXT= tzdata.zi leapseconds @@ -656,7 +665,8 @@ yearistype: yearistype.sh chmod +x yearistype leapseconds: $(LEAP_DEPS) - $(AWK) -f leapseconds.awk leap-seconds.list >$@.out + $(AWK) -v EXPIRES_LINE=$(EXPIRES_LINE) \ + -f leapseconds.awk leap-seconds.list >$@.out mv $@.out $@ # Arguments to pass to submakes of install_data. diff --git a/NEWS b/NEWS index 72e5c04..e1ac4a9 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Unreleased, experimental changes Briefly: America/Nuuk renamed from America/Godthab. + zic now supports expiration dates for leap second lists. Changes to timezone identifiers @@ -21,6 +22,19 @@ Unreleased, experimental changes from 01:59:32.999... to 02:59:33 instead of the correct transition from 01:59:59.999... to 03:00:00. + zic -L now supports an Expires line in the leapseconds file, and + truncates the TZif output accordingly. This propagates leap + second expiration information into the TZif file, and avoids the + abovementioned localtime.c bug as well as similar bugs present in + many client implementations. If no Expires line is present, zic + -L instead truncates the TZif output based on the #expires comment + present in leapseconds files distributed by tzdb 2018f and later; + however, this usage is obsolescent. For now, the distributed + leapseconds file has an Expires line that is commented out, so + that the file can be fed to older versions of zic which ignore the + commented-out line. Future tzdb distributions are planned to + contain a leapseconds file with an Expires line. + The configuration macros HAVE_TZNAME and USG_COMPAT should now be set to 1 if the system library supports the feature, and 2 if not. As before, these macros are nonzero if tzcode should support the diff --git a/leapseconds.awk b/leapseconds.awk index 74bcda0..924ade9 100755 --- a/leapseconds.awk +++ b/leapseconds.awk @@ -100,6 +100,17 @@ BEGIN { } END { + sstamp_to_ymdhMs(expires, ss_NTP) + + print "" + print "# UTC timestamp when this leap second list expires." + print "# Any additional leap seconds will come after this." + print "# This Expires line is commented out for now," + print "# so that pre-2020a zic implementations do not reject this file." + printf "%sExpires %.4d\t%s\t%.2d\t%.2d:%.2d:%.2d\n", \ + EXPIRES_LINE ? "" : "#", \ + ss_year, monthabbr[ss_month], ss_mday, ss_hour, ss_min, ss_sec + # The difference between the NTP and POSIX epochs is 70 years # (including 17 leap days), each 24 hours of 60 minutes of 60 # seconds each. diff --git a/zic.8 b/zic.8 index dc0220f..0a64fbe 100644 --- a/zic.8 +++ b/zic.8 @@ -606,7 +606,9 @@ However, the behavior is unspecified if multiple zone or link lines define the same name, or if the source of one link line is the target of another. .PP -Lines in the file that describes leap seconds have the following form: +The file that describes leap seconds can have leap lines and an +expiration line. +Leap lines have the following form: .nf .ti +.5i .ta \w'Leap\0\0'u +\w'YEAR\0\0'u +\w'MONTH\0\0'u +\w'DAY\0\0'u +\w'HH:MM:SS\0\0'u +\w'CORR\0\0'u @@ -646,6 +648,43 @@ or .q "Rolling" if the leap second time given by the other fields should be interpreted as local (wall clock) time. +.PP +The expiration line, if present, has the form: +.nf +.ti +.5i +.ta \w'Expires\0\0'u +\w'YEAR\0\0'u +\w'MONTH\0\0'u +\w'DAY\0\0'u +.sp +Expires YEAR MONTH DAY HH:MM:SS +.sp +For example: +.ti +.5i +.sp +Expires 2020 Dec 28 00:00:00 +.sp +.fi +The +.BR YEAR , +.BR MONTH , +.BR DAY , +and +.B HH:MM:SS +fields give the expiration timestamp in UTC for the leap second table; +.B zic +outputs this expiration timestamp by truncating the end of the output +file to the timestamp. +If there is no expiration line, +.B zic +also accepts a comment +.q "#expires \fIE\fP ...\&" +where +.I E +is the expiration timestamp as a decimal integer count of seconds +since the Epoch, not counting leap seconds. +However, the +.q "#expires" +comment is an obsolescent feature, +and the leap second file should use an expiration line +instead of relying on a comment. .SH "EXTENDED EXAMPLE" Here is an extended example of .B zic diff --git a/zic.c b/zic.c index 8eb0af3..2875b55 100644 --- a/zic.c +++ b/zic.c @@ -160,6 +160,7 @@ static void dolink(const char *, const char *, bool); static char ** getfields(char * buf); static zic_t gethms(const char * string, const char * errstring); static zic_t getsave(char *, bool *); +static void inexpires(char **, int); static void infile(const char * filename); static void inleap(char ** fields, int nfields); static void inlink(char ** fields, int nfields); @@ -224,6 +225,7 @@ static int typecnt; #define LC_ZONE 1 #define LC_LINK 2 #define LC_LEAP 3 +#define LC_EXPIRES 4 /* ** Which fields are which on a Zone line. @@ -289,6 +291,9 @@ static int typecnt; #define LP_ROLL 6 #define LEAP_FIELDS 7 +/* Expires lines are like Leap lines, except without CORR and ROLL fields. */ +#define EXPIRES_FIELDS 5 + /* ** Year synonyms. */ @@ -332,6 +337,7 @@ static struct lookup const zi_line_codes[] = { }; static struct lookup const leap_line_codes[] = { { "Leap", LC_LEAP }, + { "Expires", LC_EXPIRES }, { NULL, 0} }; @@ -613,6 +619,12 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); +/* The time specified by an Expires line, or negative if no such line. */ +static zic_t leapexpires = -1; + +/* The time specified by an #expires comment, or negative if no such line. */ +static zic_t comment_leapexpires = -1; + /* Set the time range of the output to TIMERANGE. Return true if successful. */ static bool @@ -1206,7 +1218,8 @@ infile(const char *name) ++nfields; } if (nfields == 0) { - /* nothing to do */ + if (name == leapsec && *buf == '#') + sscanf(buf, "#expires %"SCNdZIC, &comment_leapexpires); } else if (wantcont) { wantcont = inzcont(fields, nfields); } else { @@ -1231,6 +1244,10 @@ infile(const char *name) inleap(fields, nfields); wantcont = false; break; + case LC_EXPIRES: + inexpires(fields, nfields); + wantcont = false; + break; default: /* "cannot happen" */ fprintf(stderr, _("%s: panic: Invalid l_value %d\n"), @@ -1488,8 +1505,8 @@ inzsub(char **fields, int nfields, bool iscont) return hasuntil; } -static void -inleap(char **fields, int nfields) +static zic_t +getleapdatetime(char **fields, int nfields, bool expire_line) { register const char * cp; register const struct lookup * lp; @@ -1500,10 +1517,6 @@ inleap(char **fields, int nfields) zic_t t; char xs; - if (nfields != LEAP_FIELDS) { - error(_("wrong number of fields on Leap line")); - return; - } dayoff = 0; cp = fields[LP_YEAR]; if (sscanf(cp, "%"SCNdZIC"%c", &year, &xs) != 1) { @@ -1511,13 +1524,15 @@ inleap(char **fields, int nfields) ** Leapin' Lizards! */ error(_("invalid leaping year")); - return; + return -1; } - if (!leapseen || leapmaxyear < year) + if (!expire_line) { + if (!leapseen || leapmaxyear < year) leapmaxyear = year; - if (!leapseen || leapminyear > year) + if (!leapseen || leapminyear > year) leapminyear = year; - leapseen = true; + leapseen = true; + } j = EPOCH_YEAR; while (j != year) { if (year > j) { @@ -1531,7 +1546,7 @@ inleap(char **fields, int nfields) } if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) { error(_("invalid month name")); - return; + return -1; } month = lp->l_value; j = TM_JANUARY; @@ -1544,44 +1559,60 @@ inleap(char **fields, int nfields) if (sscanf(cp, "%d%c", &day, &xs) != 1 || day <= 0 || day > len_months[isleap(year)][month]) { error(_("invalid day of month")); - return; + return -1; } dayoff = oadd(dayoff, day - 1); if (dayoff < min_time / SECSPERDAY) { error(_("time too small")); - return; + return -1; } if (dayoff > max_time / SECSPERDAY) { error(_("time too large")); - return; + return -1; } t = dayoff * SECSPERDAY; tod = gethms(fields[LP_TIME], _("invalid time of day")); - cp = fields[LP_CORR]; - { - int correction; + t = tadd(t, tod); + if (t < 0) + error(_("leap second precedes Epoch")); + return t; +} - if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ - correction = -1; - } else if (strcmp(cp, "+") == 0) { - correction = 1; - } else { - error(_("illegal CORRECTION field on Leap line")); - return; - } - if ((lp = byword(fields[LP_ROLL], leap_types)) == NULL) { - error(_( - "illegal Rolling/Stationary field on Leap line" - )); - return; - } - t = tadd(t, tod); - if (t < 0) { - error(_("leap second precedes Epoch")); - return; - } - leapadd(t, correction, lp->l_value); - } +static void +inleap(char **fields, int nfields) +{ + if (nfields != LEAP_FIELDS) + error(_("wrong number of fields on Leap line")); + else { + zic_t t = getleapdatetime(fields, nfields, false); + if (0 <= t) { + struct lookup const *lp = byword(fields[LP_ROLL], leap_types); + if (!lp) + error(_("invalid Rolling/Stationary field on Leap line")); + else { + int correction = 0; + if (!fields[LP_CORR][0]) /* infile() turns "-" into "". */ + correction = -1; + else if (strcmp(fields[LP_CORR], "+") == 0) + correction = 1; + else + error(_("invalid CORRECTION field on Leap line")); + if (correction) + leapadd(t, correction, lp->l_value); + } + } + } +} + +static void +inexpires(char **fields, int nfields) +{ + if (nfields != EXPIRES_FIELDS) + error(_("wrong number of fields on Expires line")); + else if (0 <= leapexpires) + error(_("multiple Expires lines")); + else + leapexpires = getleapdatetime(fields, nfields, true); } static void @@ -3005,6 +3036,22 @@ adjleap(void) trans[i] = tadd(trans[i], last); last = corr[i] += last; } + + if (leapexpires < 0) { + leapexpires = comment_leapexpires; + if (0 <= leapexpires) + warning(_("\"#expires\" is obsolescent; use \"Expires\"")); + } + + if (0 <= leapexpires) { + leapexpires = oadd(leapexpires, last); + if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) { + error(_("last Leap time does not precede Expires time")); + exit(EXIT_FAILURE); + } + if (leapexpires <= hi_time) + hi_time = leapexpires - 1; + } } static char * -- 2.24.1
participants (3)
-
Michael H Deckers
-
Paul Eggert
-
Steve Allen