[tz] [PATCH] Support time stamps past 2038 in zones like America/Santiago.

Paul Eggert eggert at cs.ucla.edu
Fri Sep 6 01:31:13 UTC 2013


This implements a suggestion by Arthur David Olson.
Without this change, zic -v diagnoses problems with several zones
where it cannot compute a POSIX-equivalent TZ setting for time
stamps past 2038, which means these time stamps may be mishandled.
This entails a minor change to the binary tz file format, to allow
a minor extension to the POSIX TZ setting in the binary file,
instead of requiring a pure POSIX TZ setting.  The zones fixed by
this change are America/Godthab, America/Santiago, Antarctica/Palmer,
Asia/Gaza, Asia/Hebron, Asia/Jerusalem, Pacific/Easter, and
Pacific/Fiji.  The only zone that remains unfixed is
Asia/Tehran, which schedules clock transitions via the
Iranian calendar, something that even the extended TZ
setting cannot represent.
* localtime.c (getrule): Allow transition times to be signed.
* newtzset.3: Describe the extensions to POSIX TZ strings.
Some of these extensions (e.g., hours == 26) were already
implemented but were not documented.  Give examples.
* tzfile.5: Document the relaxed restriction on the stored TZ
string; its hours component can be in the range -167..167 rather
than the POSIX-required 0..24.  Refer to newtzset(5).
* zic.c (stringoffset): Allow hours to go up to 167.
(stringrule): Be willing to generate hours in the range -167
through 167.
---
 localtime.c |  2 +-
 newtzset.3  | 37 ++++++++++++++++++++++++++++++++++---
 tzfile.5    |  6 +++++-
 zic.c       | 27 ++++++++++++++-------------
 4 files changed, 54 insertions(+), 18 deletions(-)

diff --git a/localtime.c b/localtime.c
index 619a656..91a3171 100644
--- a/localtime.c
+++ b/localtime.c
@@ -835,7 +835,7 @@ getrule(const char *strp, register struct rule *const rulep)
 		** Time specified.
 		*/
 		++strp;
-		strp = getsecs(strp, &rulep->r_time);
+		strp = getoffset(strp, &rulep->r_time);
 	} else	rulep->r_time = 2 * SECSPERHOUR;	/* default = 2:00:00 */
 	return strp;
 }
diff --git a/newtzset.3 b/newtzset.3
index 3689e50..bb40c01 100644
--- a/newtzset.3
+++ b/newtzset.3
@@ -177,16 +177,47 @@ The
 .I time
 has the same format as
 .I offset
-except that no leading sign
+except that POSIX does not allow a leading sign
 .RB (`` \(mi ''
 or
-.RB `` \(pl '')
-is allowed.  The default, if
+.RB `` \(pl '').
+As an extension to POSIX, the hours part of
+.I time
+can range from \(mi167 to 167; this allows for unusual rules such
+as "the Saturday before the first Sunday of March".  The default, if
 .I time
 is not given, is
 .BR 02:00:00 .
 .RE
 .LP
+Here are some examples of
+.B TZ
+values that directly specify the time zone rules; they use some of the
+extensions to POSIX.
+.TP
+.B EST5
+stands for US eastern
+time (EST), 5 hours behind UTC, without daylight saving.
+.TP
+.B FJT\(mi12FJST,M10.3.4/74,M1.3.4/75
+stands for Fiji Time (FJT) and Fiji Summer Time (FJST), 12 hours ahead
+of UTC, where clocks spring forward at 74:00 on the third Thursday in
+October (i.e., 02:00 on the first Sunday on or after October 18), and
+fall back at 75:00 on the third Thursday in January (i.e., 03:00 on
+the first Sunday on or after January 18).
+.TP
+.B IST\(mi2IDT,M3.4.4/26,M10.5.0
+stands for Israel standard time (IST) and Israel daylight time (IDT),
+2 hours ahead of UTC, where clocks spring forward at 26:00 on the
+fourth Thursday in March (i.e., 02:00 on the first Friday on or after
+March 23), and fall back at 02:00 on the last Sunday in October.
+.TP
+.B WGT3WGST,M3.5.0/\(mi2,M10.5.0/\(mi1
+stands for Western Greenland Time (WGT) and Western Greenland Summer
+Time (WGST), 3 hours behind UTC, where clocks follow the EU rules of
+springing forward the last Sunday in March at \(mi02:00 (i.e., 01:00 UTC)
+and falling back the last Sunday in October at \(mi01:00 (i.e., 01:00 UTC).
+.PP
 If no
 .I rule
 is present in
diff --git a/tzfile.5 b/tzfile.5
index e92eaed..c7bd40e 100644
--- a/tzfile.5
+++ b/tzfile.5
@@ -145,7 +145,11 @@ 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).
+This string may use a minor extension to the POSIX TZ format: 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.
 .SH SEE ALSO
-newctime(3)
+newctime(3), newtzset(3)
 .\" This file is in the public domain, so clarified as of
 .\" 1996-06-05 by Arthur David Olson.
diff --git a/zic.c b/zic.c
index 97786ae..260dc2e 100644
--- a/zic.c
+++ b/zic.c
@@ -1776,7 +1776,7 @@ stringoffset(char *result, zic_t offset)
 	minutes = offset % MINSPERHOUR;
 	offset /= MINSPERHOUR;
 	hours = offset;
-	if (hours > HOURSPERDAY) {
+	if (hours >= HOURSPERDAY * DAYSPERWEEK) {
 		result[0] = '\0';
 		return -1;
 	}
@@ -1793,7 +1793,7 @@ static int
 stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 	   const zic_t gmtoff)
 {
-	register zic_t	tod;
+	register zic_t	tod = rp->r_tod;
 
 	result = end(result);
 	if (rp->r_dycode == DC_DOM) {
@@ -1807,32 +1807,33 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 		(void) sprintf(result, "J%d", total + rp->r_dayofmonth);
 	} else {
 		register int	week;
+		register int	wday = rp->r_wday;
+		register int	wdayoff;
 
 		if (rp->r_dycode == DC_DOWGEQ) {
-			if ((rp->r_dayofmonth % DAYSPERWEEK) != 1)
-				return -1;
-			week = 1 + rp->r_dayofmonth / DAYSPERWEEK;
+			wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK;
+			wday -= wdayoff;
+			tod += wdayoff * SECSPERDAY;
+			week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK;
 		} else if (rp->r_dycode == DC_DOWLEQ) {
 			if (rp->r_dayofmonth == len_months[1][rp->r_month])
 				week = 5;
 			else {
-				if ((rp->r_dayofmonth % DAYSPERWEEK) != 0)
-					return -1;
+				wdayoff = rp->r_dayofmonth % DAYSPERWEEK;
+				wday -= wdayoff;
+				tod += wdayoff * SECSPERDAY;
 				week = rp->r_dayofmonth / DAYSPERWEEK;
 			}
 		} else	return -1;	/* "cannot happen" */
+		if (wday < 0)
+			wday += DAYSPERWEEK;
 		(void) sprintf(result, "M%d.%d.%d",
-			rp->r_month + 1, week, rp->r_wday);
+			rp->r_month + 1, week, wday);
 	}
-	tod = rp->r_tod;
 	if (rp->r_todisgmt)
 		tod += gmtoff;
 	if (rp->r_todisstd && rp->r_stdoff == 0)
 		tod += dstoff;
-	if (tod < 0) {
-		result[0] = '\0';
-		return -1;
-	}
 	if (tod != 2 * SECSPERMIN * MINSPERHOUR) {
 		(void) strcat(result, "/");
 		if (stringoffset(end(result), tod) != 0)
-- 
1.8.1.2



More information about the tz mailing list