[PATCH 1/7] localtime.c is less finicky about leap seconds
* localtime.c (tzloadbody): Do not reject a TZif file merely because its leap second table contains adjacent entries with equal corrections, or contains a first entry with a correction value other than +1 or -1. * NEWS: Mention this. --- NEWS | 5 +++++ localtime.c | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 0de05f3..d8676e6 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,11 @@ Unreleased, experimental changes seconds error than with an hour error, so zic -L no longer truncates output in this way. + The TZif reader now allows the leap second table to begin with a + correction other than -1 or +1, and to contain adjacent + transitions with equal corrections. This supports possible + future extensions to the TZif format. + Fix bug that caused 'localtime' etc. to crash when TZ was set to a all-year DST string like "EST5EDT4,0/0,J365/25" that does not conform to POSIX but does conform to Internet RFC 8536. diff --git a/localtime.c b/localtime.c index 6627ff2..a086d4d 100644 --- a/localtime.c +++ b/localtime.c @@ -445,7 +445,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend, int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); int_fast64_t prevtr = 0; - int_fast32_t prevcorr = 0; + int_fast32_t prevcorr; int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); @@ -542,9 +542,16 @@ tzloadbody(char const *name, struct state *sp, bool doextend, and UTC months are at least 28 days long (minus 1 second for a negative leap second). Each leap second's correction must differ from the previous one's by 1 - second. */ + second or less, except that the first correction can be + any value; these requirements are more generous than + RFC 8536, to allow future RFC extensions. */ if (tr - prevtr < 28 * SECSPERDAY - 1 - || (corr != prevcorr - 1 && corr != prevcorr + 1)) + || ((timecnt == 0 || sp->ats[0] < tr) + && ! (i == 0 + || (prevcorr < corr + ? corr == prevcorr + 1 + : (corr == prevcorr + || corr == prevcorr - 1))))) return EINVAL; sp->lsis[leapcnt].ls_trans = prevtr = tr; sp->lsis[leapcnt].ls_corr = prevcorr = corr; -- 2.27.0
* localtime.c (struct lsinfo, timesub, leapcorr): Use int_fast32_t, not int_fast64_t, for leap second corrections, since these values always fit into 32 bits in TZif files. --- localtime.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/localtime.c b/localtime.c index a086d4d..5b8f278 100644 --- a/localtime.c +++ b/localtime.c @@ -99,7 +99,7 @@ struct ttinfo { /* time type information */ struct lsinfo { /* leap second information */ time_t ls_trans; /* transition time */ - int_fast64_t ls_corr; /* correction to apply */ + int_fast32_t ls_corr; /* correction to apply */ }; #define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) @@ -150,7 +150,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 int_fast32_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 *); @@ -1652,7 +1652,7 @@ timesub(const time_t *timep, int_fast32_t offset, register int_fast64_t rem; int y; register const int * ip; - register int_fast64_t corr; + register int_fast32_t corr; register bool hit; register int i; @@ -2228,7 +2228,7 @@ timeoff(struct tm *tmp, long offset) #endif /* defined STD_INSPIRED */ -static int_fast64_t +static int_fast32_t leapcorr(struct state const *sp, time_t t) { register struct lsinfo const * lp; -- 2.27.0
* NEWS: Mention this. * localtime.c (tzloadbody): Don’t misbehave if adding the leap second correction overflows time_t. --- NEWS | 4 ++++ localtime.c | 19 +++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index d8676e6..5b26bd2 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,10 @@ 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 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. + Fix bug in zic -r; in some cases, the dummy time type after the last time transition disagreed with the TZ string, contrary to Internet RFC 8563 section 3.3. diff --git a/localtime.c b/localtime.c index 5b8f278..0d34ead 100644 --- a/localtime.c +++ b/localtime.c @@ -639,19 +639,18 @@ tzloadbody(char const *name, struct state *sp, bool doextend, == sp->types[sp->timecnt - 2])) sp->timecnt--; - for (i = 0; i < ts->timecnt; i++) - if (sp->timecnt == 0 - || (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] + leapcorr(sp, ts->ats[i]); + for (i = 0; + i < ts->timecnt && sp->timecnt < TZ_MAX_TIMES; + i++) { + time_t t = ts->ats[i]; + if (increment_overflow_time(&t, leapcorr(sp, t)) + || (0 < sp->timecnt + && t <= sp->ats[sp->timecnt - 1])) + continue; + sp->ats[sp->timecnt] = t; sp->types[sp->timecnt] = (sp->typecnt + ts->types[i]); sp->timecnt++; - i++; } for (i = 0; i < ts->typecnt; i++) sp->ttis[sp->typecnt++] = ts->ttis[i]; -- 2.27.0
* Makefile, NEWS, tzfile.5, zic.8: Document the change. * zic.c (ZIC_VERSION_PRE_2013, ZIC_VERSION, comment_leapexpires): Remove. All uses removed. (infile): Don’t bother parsing #expires lines any more; only Expires lines matter now. (struct timerange): New member leapexpiry. (limitrange, writezone): Set it. (writezone): If leap seconds are generated, append a no-op leap correction indicating when leap seconds expire. Generate version 4 files (with a warning if -v) if the leap second table is truncated from below, or expires. Fix bug if thisleapcnt changes after thisleaplim is set. --- Makefile | 6 ++++-- NEWS | 18 ++++++++++++++-- tzfile.5 | 19 +++++++++++++--- zic.8 | 25 ++++++++++----------- zic.c | 66 ++++++++++++++++++++++++++++++++++++++++---------------- 5 files changed, 94 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 0dc44ae..d05f828 100644 --- a/Makefile +++ b/Makefile @@ -152,8 +152,10 @@ REDO= posix_right # 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. +# and was modified in tzdb 2021b to generate version 4 TZif files. +# EXPIRES_LINE defaults to 0 for now so that the leapseconds file +# can be given to pre-2020a zic implementations and so that TZif files +# built by newer zic implementations can be read by pre-2021b libraries. EXPIRES_LINE= 0 # To install data in text form that has all the information of the TZif data, diff --git a/NEWS b/NEWS index 5b26bd2..2e6b443 100644 --- a/NEWS +++ b/NEWS @@ -39,10 +39,24 @@ Unreleased, experimental changes seconds error than with an hour error, so zic -L no longer truncates output in this way. + Instead, when zic -L is given the "Expires" directive, it now + outputs the expiration by appending a no-change entry to the leap + second table. Although this should work well with most TZif + readers, it does not conform to Internet RFC 8536 and some pickier + clients (including tzdb 2017c through 2021a) reject it, so + "Expires" directives are currently disabled by default. To enable + them, set the EXPIRES_LINE Makefile variable. If a TZif file uses + this new feature it is marked with a new TZif version number 4. + + zic -L LEAPFILE -r @LO no longer no longer generates an invalid + TZif file that omits leap second information for the range LO..B + when LO falls between two leap seconds A and B. Instead, it + generates a TZif version 4 file that represents the + previously-missing information. + The TZif reader now allows the leap second table to begin with a correction other than -1 or +1, and to contain adjacent - transitions with equal corrections. This supports possible - future extensions to the TZif format. + transitions with equal corrections. This supports TZif version 4. Fix bug that caused 'localtime' etc. to crash when TZ was set to a all-year DST string like "EST5EDT4,0/0,J365/25" that does diff --git a/tzfile.5 b/tzfile.5 index 2642978..bc41032 100644 --- a/tzfile.5 +++ b/tzfile.5 @@ -30,10 +30,11 @@ The magic four-byte ASCII sequence identifies the file as a timezone information file. .IP * A byte identifying the version of the file's format -(as of 2017, either an ASCII NUL, or +(as of 2021, either an ASCII NUL, .q "2", +.q "3", or -.q "3" ). +.q "4" ). .IP * Fifteen bytes containing zeros reserved for future use. .IP * @@ -134,13 +135,15 @@ the first value of each pair gives the nonnegative time (as returned by .BR time (2)) at which a leap second occurs; -the second is a signed integer specifying the +the second is a signed integer specifying the correction, which is the .I total number of leap seconds to be applied during the time period starting at the given time. The pairs of values are sorted in ascending order by time. Each transition is for one leap second, either positive or negative; transitions always separated by at least 28 days minus 1 second. +The first entry's correction is +1 (or \(mi1, for a hypothetical leap +second table where the first leap second was negative). .IP * .B tzh_ttisstdcnt standard/wall indicators, each stored as a one-byte boolean; @@ -212,6 +215,16 @@ from 0 through 24. Second, DST is in effect all year if it starts January 1 at 00:00 and ends December 31 at 24:00 plus the difference between daylight saving and standard time. +.SS Version 4 format +For version-4-format TZif files, +the first leap second transition can have a correction that is neither ++1 nor \(mi1, to support TZif files with reduced timestamp range. +Also, if two or more leap second transitions are present and the last +entry's correction equals the previous one, the last entry +denotes the expiration of the leap second table instead of a leap second; +timestamps after this expiration are unreliable in that future +releases will likely add leap second entries after the expiration, and +the added leap seconds will change how post-expiration timestamps are treated. .SS Interoperability considerations Future changes to the format may append more data. .PP diff --git a/zic.8 b/zic.8 index 217cc08..800fb8b 100644 --- a/zic.8 +++ b/zic.8 @@ -31,7 +31,8 @@ zic \- timezone compiler The .B zic program reads text from the file(s) named on the command line -and creates the time conversion information files specified in this input. +and creates the timezone information format (TZif) files +specified in this input. If a .I filename is @@ -213,6 +214,15 @@ code designed for older output formats. These compatibility issues affect only timestamps before 1970 or after the start of 2038. .PP +The output contains a truncated leap second table, +which can cause some older TZif readers to misbehave. +This can occur if the +.B "\*-L" +option is used, and either an Expires line is present or +the +.B "\*-r" +option is also used. +.PP The output file contains more than 1200 transitions, which may be mishandled by some clients. The current reference client supports at most 2000 transitions; @@ -720,19 +730,6 @@ The and .B HH:MM:SS fields give the expiration timestamp in UTC for the leap second table; -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 b3cfd71..f5d813b 100644 --- a/zic.c +++ b/zic.c @@ -15,9 +15,6 @@ #include <stddef.h> #include <stdio.h> -#define ZIC_VERSION_PRE_2013 '2' -#define ZIC_VERSION '3' - typedef int_fast64_t zic_t; #define ZIC_MIN INT_FAST64_MIN #define ZIC_MAX INT_FAST64_MAX @@ -681,9 +678,6 @@ 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 @@ -1354,8 +1348,7 @@ infile(const char *name) ++nfields; } if (nfields == 0) { - if (name == leapsec && *buf == '#') - sscanf(buf, "#expires %"SCNdZIC, &comment_leapexpires); + /* nothing to do */ } else if (wantcont) { wantcont = inzcont(fields, nfields); } else { @@ -1975,6 +1968,7 @@ struct timerange { int defaulttype; ptrdiff_t base, count; int leapbase, leapcount; + bool leapexpiry; }; static struct timerange @@ -1986,7 +1980,7 @@ limitrange(struct timerange r, zic_t lo, zic_t hi, r.count--; r.base++; } - while (0 < r.leapcount && trans[r.leapbase] < lo) { + while (1 < r.leapcount && trans[r.leapbase + 1] <= lo) { r.leapcount--; r.leapbase++; } @@ -1997,6 +1991,7 @@ limitrange(struct timerange r, zic_t lo, zic_t hi, while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1]) r.leapcount--; } + r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi; return r; } @@ -2107,9 +2102,36 @@ writezone(const char *const name, const char *const string, char version, rangeall.base = rangeall.leapbase = 0; rangeall.count = timecnt; rangeall.leapcount = leapcnt; + rangeall.leapexpiry = false; range64 = limitrange(rangeall, lo_time, hi_time, ats, types); range32 = limitrange(range64, INT32_MIN, INT32_MAX, ats, types); + /* TZif version 4 is needed if a no-op transition is appended to + indicate the expiration of the leap second table, or if the first + leap second transition is not to a +1 or -1 correction. */ + for (pass = 1; pass <= 2; pass++) { + struct timerange const *r = pass == 1 ? &range32 : &range64; + if (pass == 1 && !want_bloat()) + continue; + if (r->leapexpiry) { + if (noise) + warning(_("%s: pre-2021b clients may mishandle" + " leap second expiry"), + name); + version = '4'; + } + if (0 < r->leapcount + && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) { + if (noise) + warning(_("%s: pre-2021b clients may mishandle" + " leap second table truncation"), + name); + version = '4'; + } + if (version == '4') + break; + } + fp = open_outfile(&outname, &tempname); for (pass = 1; pass <= 2; ++pass) { @@ -2117,7 +2139,7 @@ writezone(const char *const name, const char *const string, char version, register int thisleapi, thisleapcnt, thisleaplim; struct tzhead tzh; int currenttype, thisdefaulttype; - bool locut, hicut; + bool locut, hicut, thisleapexpiry; zic_t lo; int old0; char omittype[TZ_MAX_TYPES]; @@ -2148,6 +2170,7 @@ writezone(const char *const name, const char *const string, char version, toomanytimes = thistimecnt >> 31 >> 1 != 0; thisleapi = range32.leapbase; thisleapcnt = range32.leapcount; + thisleapexpiry = range32.leapexpiry; locut = INT32_MIN < lo_time; hicut = hi_time < INT32_MAX; } else { @@ -2157,6 +2180,7 @@ writezone(const char *const name, const char *const string, char version, toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0; thisleapi = range64.leapbase; thisleapcnt = range64.leapcount; + thisleapexpiry = range64.leapexpiry; locut = min_time < lo_time; hicut = hi_time < max_time; } @@ -2177,7 +2201,6 @@ writezone(const char *const name, const char *const string, char version, } thistimelim = thistimei + thistimecnt; - thisleaplim = thisleapi + thisleapcnt; if (thistimecnt != 0) { if (ats[thistimei] == lo_time) locut = false; @@ -2279,6 +2302,7 @@ writezone(const char *const name, const char *const string, char version, } if (pass == 1 && !want_bloat()) { thisleapcnt = 0; + thisleapexpiry = false; thistimecnt = - (locut + hicut); thistypecnt = thischarcnt = 1; thistimelim = thistimei; @@ -2289,7 +2313,7 @@ writezone(const char *const name, const char *const string, char version, tzh.tzh_version[0] = version; convert(utcnt, tzh.tzh_ttisutcnt); convert(stdcnt, tzh.tzh_ttisstdcnt); - convert(thisleapcnt, tzh.tzh_leapcnt); + convert(thisleapcnt + thisleapexpiry, tzh.tzh_leapcnt); convert(locut + thistimecnt + hicut, tzh.tzh_timecnt); convert(thistypecnt, tzh.tzh_typecnt); convert(thischarcnt, tzh.tzh_charcnt); @@ -2347,6 +2371,7 @@ writezone(const char *const name, const char *const string, char version, if (thischarcnt != 0) fwrite(thischars, sizeof thischars[0], thischarcnt, fp); + thisleaplim = thisleapi + thisleapcnt; for (i = thisleapi; i < thisleaplim; ++i) { register zic_t todo; @@ -2370,6 +2395,15 @@ writezone(const char *const name, const char *const string, char version, puttzcodepass(todo, fp, pass); puttzcode(corr[i], fp); } + if (thisleapexpiry) { + /* Append a no-op leap correction indicating when the leap + second table expires. Although this does not conform to + Internet RFC 8536, most clients seem to accept this and + the plan is to amend the RFC to allow this in version 4 + TZif files. */ + puttzcodepass(leapexpires, fp, pass); + puttzcode(thisleaplim ? corr[thisleaplim - 1] : 0, fp); + } if (stdcnt != 0) for (i = old0; i < typecnt; i++) if (!omittype[i]) @@ -2779,7 +2813,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) ** Generate lots of data if a rule can't cover all future times. */ compat = stringzone(envvar, zpfirst, zonecount); - version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION; + version = compat < 2013 ? '2' : '3'; do_extend = compat < 0; if (noise) { if (!*envvar) @@ -3154,12 +3188,6 @@ adjleap(void) 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))) { -- 2.27.0
* localtime.c (tzparse): Simplify by removing the LASTDITCH parameter. All uses simplified. The only caller that used it can pass "GMT0" instead of "GMT", for equivalent effect. --- localtime.c | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/localtime.c b/localtime.c index 0d34ead..aab806a 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 *, bool); +static bool tzparse(char const *, struct state *); #ifdef ALL_STATE static struct state * lclptr; @@ -598,7 +598,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend, struct state *ts = &lsp->u.st; up->buf[nread - 1] = '\0'; - if (tzparse(&up->buf[1], ts, false)) { + if (tzparse(&up->buf[1], ts)) { /* Attempt to reuse existing abbreviations. Without this, America/Anchorage would be right on @@ -1064,7 +1064,7 @@ transtime(const int year, register const struct rule *const rulep, */ static bool -tzparse(const char *name, struct state *sp, bool lastditch) +tzparse(const char *name, struct state *sp) { const char * stdname; const char * dstname; @@ -1077,29 +1077,23 @@ tzparse(const char *name, struct state *sp, bool lastditch) register bool load_ok; stdname = name; - if (lastditch) { - stdlen = sizeof gmt - 1; - name += stdlen; - stdoffset = 0; + if (*name == '<') { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + stdlen = name - stdname; + name++; } else { - if (*name == '<') { - name++; - stdname = name; - name = getqzname(name, '>'); - if (*name != '>') - return false; - stdlen = name - stdname; - name++; - } else { - name = getzname(name); - stdlen = name - stdname; - } - if (!stdlen) - return false; - name = getoffset(name, &stdoffset); - if (name == NULL) - return false; + name = getzname(name); + stdlen = name - stdname; } + if (!stdlen) + return false; + name = getoffset(name, &stdoffset); + if (name == NULL) + return false; charcnt = stdlen + 1; if (sizeof sp->chars < charcnt) return false; @@ -1318,7 +1312,7 @@ static void gmtload(struct state *const sp) { if (tzload(gmt, sp, true) != 0) - tzparse(gmt, sp, true); + tzparse("GMT0", sp); } /* Initialize *SP to a value appropriate for the TZ setting NAME. @@ -1341,7 +1335,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, false)) + if (err != 0 && name && name[0] != ':' && tzparse(name, sp)) err = 0; if (err == 0) scrub_abbrs(sp); -- 2.27.0
* 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
Without this fix, on the rare installations where time_t counts leap seconds (e.g., make REDO=right_only), ‘zdump -v America/Los_Angeles’ would output a transition like this: Sun Jan 1 00:00:26 2017 UT = Sat Dec 31 16:59:60 2016 PDT Sun Jan 1 00:00:27 2017 UT = Sat Dec 31 17:00:00 2016 PDT which is incorrect, as the left-hand column is TAI, not UT. With this patch, the same command outputs this instead: Sat Dec 31 23:59:60 2016 UT = Sat Dec 31 16:59:60 2016 PDT Sun Jan 1 00:00:00 2017 UT = Sat Dec 31 17:00:00 2016 PDT * NEWS: Mention this. * zdump.c (gmtzinit): Try "GMT" and fall back on "GMT0" rather than using "UTC0". --- NEWS | 4 ++++ zdump.c | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 559e96e..bccd6c2 100644 --- a/NEWS +++ b/NEWS @@ -86,6 +86,10 @@ Unreleased, experimental changes "EST5EDT,0/0,J365/25" or "". (Thanks to Michael Deckers for noting the possibility of POSIX conformance.) + When reading slim TZif files, zdump no longer mishandles leap + seconds on the rare platforms where time_t counts leap seconds, + fixing a bug introduced in 2014g. + zdump's -c and -t options are now consistently inclusive for the lower time bound and exclusive for the upper. Formerly they were inconsistent. (Confusion noted by Martin Burnicki.) diff --git a/zdump.c b/zdump.c index c29f059..dcad817 100644 --- a/zdump.c +++ b/zdump.c @@ -259,11 +259,23 @@ static void gmtzinit(void) { if (USE_LOCALTIME_RZ) { - static char const utc[] = "UTC0"; - gmtz = tzalloc(utc); + /* Try "GMT" first to find out whether this is one of the rare + platforms where time_t counts leap seconds; this works due to + the "Link Etc/GMT GMT" line in the "etcetera" file. If "GMT" + fails, fall back on "GMT0" which might be similar due to the + "Link Etc/GMT GMT0" line in the "backward" file, and which + should work on all POSIX platforms. The rest of zdump does not + use the "GMT" abbreviation that comes from this setting, so it + is OK to use "GMT" here rather than the more-modern "UTC" which + would not work on platforms that omit the "backward" file. */ + gmtz = tzalloc("GMT"); if (!gmtz) { - perror(utc); - exit(EXIT_FAILURE); + static char const gmt0[] = "GMT0"; + gmtz = tzalloc(gmt0); + if (!gmtz) { + perror(gmt0); + exit(EXIT_FAILURE); + } } } } -- 2.27.0
participants (2)
-
Antonio Diaz Diaz -
Paul Eggert