[tz] [PATCH 2/2] Bump tzfile format to version 3.

Paul Eggert eggert at cs.ucla.edu
Mon Sep 9 06:17:58 UTC 2013


Also, improve the documentation and diagnostics in this area.
Suggested by Arthur David Olson in
<http://mm.icann.org/pipermail/tz/2013-September/020064.html>.
* tzfile.5, tzfile.h: Bump tzfile format to version 3.
* zic.8: Document -v better.
* zic.c (ZIC_VERSION): Bump from '2' to '3'.
(stringrule, stringzone, outzone): Report compatibility issues
more carefully, mentioning client dates.
---
 tzfile.5 |  9 +++++----
 tzfile.h |  9 ++++++++-
 zic.8    | 38 ++++++++++++++++++++++++++++++-----
 zic.c    | 70 ++++++++++++++++++++++++++++++++++++++++++++++------------------
 4 files changed, 97 insertions(+), 29 deletions(-)

diff --git a/tzfile.5 b/tzfile.5
index b2d1a4d..ff1ec63 100644
--- a/tzfile.5
+++ b/tzfile.5
@@ -10,7 +10,7 @@ The time zone information files used by
 begin with the magic characters "TZif" to identify them as
 time zone information files,
 followed by a character identifying the version of the file's format
-(as of 2005, either an ASCII NUL or a '2')
+(as of 2013, either an ASCII NUL, or '2', or '3')
 followed by fifteen bytes containing zeroes reserved for future use,
 followed by six four-byte values of type
 .BR long ,
@@ -145,9 +145,10 @@ POSIX-TZ-environment-variable-style string for use in handling instants
 after the last transition time stored in the file
 (with nothing between the newlines if there is no POSIX representation for
 such instants).
-As described in
-.IR newtzset (3),
-this string may use two minor extensions to the POSIX TZ format.
+.PP
+For version-3-format time zone files, the POSIX-TZ-style string may
+use two minor extensions to the POSIX TZ format, as described in
+.IR newtzset (3).
 First, the hours part of its transition times may be signed and range from
 \(mi167 through 167 instead of the POSIX-required unsigned values
 from 0 through 24.  Second, DST is in effect all year if it starts
diff --git a/tzfile.h b/tzfile.h
index 0cf2943..63db98e 100644
--- a/tzfile.h
+++ b/tzfile.h
@@ -39,7 +39,7 @@
 
 struct tzhead {
 	char	tzh_magic[4];		/* TZ_MAGIC */
-	char	tzh_version[1];		/* '\0' or '2' as of 2005 */
+	char	tzh_version[1];		/* '\0' or '2' or '3' as of 2013 */
 	char	tzh_reserved[15];	/* reserved--must be zero */
 	char	tzh_ttisgmtcnt[4];	/* coded number of trans. time flags */
 	char	tzh_ttisstdcnt[4];	/* coded number of trans. time flags */
@@ -82,6 +82,13 @@ struct tzhead {
 ** instants after the last transition time stored in the file
 ** (with nothing between the newlines if there is no POSIX representation for
 ** such instants).
+**
+** If tz_version is '3' or greatar, the above is extended as follows.
+** First, the POSIX TZ string's hour offset may range from -167
+** through 167 as compared to the POSIX-required 0 through 24.
+** Second, its DST start time may be January 1 at 00:00 and its stop
+** time December 31 at 24:00 plus the difference between DST and
+** standard time, indicating DST all year.
 */
 
 /*
diff --git a/zic.8 b/zic.8
index 5c8b59c..602c3c9 100644
--- a/zic.8
+++ b/zic.8
@@ -77,14 +77,42 @@ If this option is not used,
 no leap second information appears in output files.
 .TP
 .B \-v
-Complain if a year that appears in a data file is outside the range
+Be more verbose, and complain about the following situations:
+.RS
+.PP
+The input data specifies a link to a link.
+.PP
+A year that appears in a data file is outside the range
 of years representable by
 .IR time (2)
 values.
-Also complain if a time of 24:00
-(which cannot be handled by pre-1998 versions of
-.IR zic )
-appears in the input.
+.PP
+A time of 24:00 or more appears in the input.
+Pre-1998 versions of
+.I zic
+prohibit 24:00, and pre-2007 versions prohibit times greater than 24:00.
+.PP
+A rule goes past the start or end of the month.
+Pre-2004 versions of
+.I zic
+prohibit this.
+.PP
+The output file does not contain all the information about the
+long-term future of a zone, because the future cannot be summarized as
+an extended POSIX TZ string.  For example, as of 2013 this problem
+occurs for Iran's daylight-saving rules for the predicted future, as
+these rules are based on the Iranian calendar, which cannot be
+represented.
+.PP
+The output contains data that may not be handled properly by client
+code designed for older
+.I zic
+output formats.  These compatibility issues affect only time stamps
+before 1970 or after the start of 2038.
+.PP
+A time zone abbreviation has fewer than 3 characters.
+POSIX requires at least 3.
+.RE
 .TP
 .B \-s
 Limit time values stored in output files to values that are the same
diff --git a/zic.c b/zic.c
index 55afb40..dcab3aa 100644
--- a/zic.c
+++ b/zic.c
@@ -10,7 +10,7 @@
 
 #include <stdarg.h>
 
-#define	ZIC_VERSION	'2'
+#define	ZIC_VERSION	'3'
 
 typedef int_fast64_t	zic_t;
 #define ZIC_MIN INT_FAST64_MIN
@@ -1795,6 +1795,7 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 	   const zic_t gmtoff)
 {
 	register zic_t	tod = rp->r_tod;
+	register int	compat = 0;
 
 	result = end(result);
 	if (rp->r_dycode == DC_DOM) {
@@ -1817,6 +1818,8 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 
 		if (rp->r_dycode == DC_DOWGEQ) {
 			wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK;
+			if (wdayoff)
+				compat = 2013;
 			wday -= wdayoff;
 			tod += wdayoff * SECSPERDAY;
 			week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK;
@@ -1825,6 +1828,8 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 				week = 5;
 			else {
 				wdayoff = rp->r_dayofmonth % DAYSPERWEEK;
+				if (wdayoff)
+					compat = 2013;
 				wday -= wdayoff;
 				tod += wdayoff * SECSPERDAY;
 				week = rp->r_dayofmonth / DAYSPERWEEK;
@@ -1843,8 +1848,15 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 		(void) strcat(result, "/");
 		if (stringoffset(end(result), tod) != 0)
 			return -1;
+		if (tod < 0) {
+			if (compat < 2013)
+				compat = 2013;
+		} else if (SECSPERDAY <= tod) {
+			if (compat < 1994)
+				compat = 1994;
+		}
 	}
-	return 0;
+	return compat;
 }
 
 static int
@@ -1861,7 +1873,7 @@ rule_cmp(struct rule const *a, struct rule const *b)
 	return a->r_dayofmonth - b->r_dayofmonth;
 }
 
-static void
+static int
 stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 {
 	register const struct zone *	zp;
@@ -1870,6 +1882,8 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 	register struct rule *		dstrp;
 	register int			i;
 	register const char *		abbrvar;
+	register int			compat = 0;
+	register int			c;
 	struct rule			stdr, dstr;
 
 	result[0] = '\0';
@@ -1884,11 +1898,11 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 		if (rp->r_stdoff == 0) {
 			if (stdrp == NULL)
 				stdrp = rp;
-			else	return;
+			else	return -1;
 		} else {
 			if (dstrp == NULL)
 				dstrp = rp;
-			else	return;
+			else	return -1;
 		}
 	}
 	if (stdrp == NULL && dstrp == NULL) {
@@ -1911,7 +1925,7 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 		** do not try to apply a rule to the zone.
 		*/
 		if (stdrp != NULL && stdrp->r_hiyear == 2037)
-			return;
+			return -1;
 
 		if (stdrp != NULL && stdrp->r_stdoff != 0) {
 			/* Perpetual DST.  */
@@ -1935,32 +1949,39 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 		}
 	}
 	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))
-		return;
+		return -1;
 	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
 	doabbr(result, zp->z_format, abbrvar, FALSE, TRUE);
 	if (stringoffset(end(result), -zp->z_gmtoff) != 0) {
 		result[0] = '\0';
-		return;
+		return -1;
 	}
 	if (dstrp == NULL)
-		return;
+		return compat;
 	doabbr(end(result), zp->z_format, dstrp->r_abbrvar, TRUE, TRUE);
 	if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR)
 		if (stringoffset(end(result),
 			-(zp->z_gmtoff + dstrp->r_stdoff)) != 0) {
 				result[0] = '\0';
-				return;
+				return -1;
 		}
 	(void) strcat(result, ",");
-	if (stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) {
+	c = stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff);
+	if (c < 0) {
 		result[0] = '\0';
-		return;
+		return -1;
 	}
+	if (compat < c)
+		compat = c;
 	(void) strcat(result, ",");
-	if (stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) {
+	c = stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff);
+	if (c < 0) {
 		result[0] = '\0';
-		return;
+		return -1;
 	}
+	if (compat < c)
+		compat = c;
+	return compat;
 }
 
 static void
@@ -1984,6 +2005,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)
 	register int			max_abbr_len;
 	register int			max_envvar_len;
 	register int			prodstic; /* all rules are min to max */
+	register int			compat;
 
 	max_abbr_len = 2 + max_format_len + max_abbrvar_len;
 	max_envvar_len = 2 * max_abbr_len + 5 * 9;
@@ -2032,11 +2054,21 @@ outzone(const struct zone * const zpfirst, const int zonecount)
 	/*
 	** Generate lots of data if a rule can't cover all future times.
 	*/
-	stringzone(envvar, zpfirst, zonecount);
-	if (noise && envvar[0] == '\0')
-		warning("%s %s",
-			_("no POSIX environment variable for zone"),
-			zpfirst->z_name);
+	compat = stringzone(envvar, zpfirst, zonecount);
+	if (noise && compat != 0) {
+		if (compat < 0)
+			warning("%s %s",
+				_("no POSIX environment variable for zone"),
+				zpfirst->z_name);
+		else {
+			/* Circa-COMPAT clients, and earlier clients, might
+			   not work for this zone when given dates before
+			   1970 or after 2038.  */
+			warning(_("%s: pre-%d clients may mishandle"
+				  " distant timestamps"),
+				zpfirst->z_name, compat);
+		}
+	}
 	if (envvar[0] == '\0') {
 		if (min_year >= ZIC_MIN + YEARSPERREPEAT)
 			min_year -= YEARSPERREPEAT;
-- 
1.8.1.2




More information about the tz mailing list