[tz] [PROPOSED 3/3] Add support for Expires lines to zic

Paul Eggert eggert at cs.ucla.edu
Wed Jan 15 02:23:03 UTC 2020


* 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



More information about the tz mailing list