summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile12
-rw-r--r--NEWS14
-rwxr-xr-xleapseconds.awk11
-rw-r--r--zic.841
-rw-r--r--zic.c125
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 *