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