diff options
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | NEWS | 14 | ||||
-rwxr-xr-x | leapseconds.awk | 11 | ||||
-rw-r--r-- | zic.8 | 41 | ||||
-rw-r--r-- | zic.c | 125 |
5 files changed, 162 insertions, 41 deletions
@@ -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. @@ -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. @@ -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 @@ -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 * |