[tz] [PATCH 2/4] Conform to POSIX for all-year DST in TZ strings

Paul Eggert eggert at cs.ucla.edu
Thu Feb 18 03:29:42 UTC 2021


zic now generates a POSIX-conforming TZ string for timezones
where all-year DST is predicted for the indefinite future.
This implements a suggestion by Michael Deckers in:
https://mm.icann.org/pipermail/tz/2020-February/028834.html
This change does not affect any existing tzdb zones,
as we prefer adjusting the standard time to all-year DST.
* zic.c (rule_cmp): Compare two rules to be the same
if they both extend into the indefinite future.
(stringzone): For DST all year, generate something like
"XXX3EDT4,0/0,J365/23" (which conforms to current POSIX) instead
of "EST5EDT4,0/0,J365/25" (which does not).  Eventually POSIX
should change to allow either form, but we might as well be
compatible with current POSIX when we can.
---
 zic.c | 127 +++++++++++++++++++++++++++++++---------------------------
 1 file changed, 67 insertions(+), 60 deletions(-)

diff --git a/zic.c b/zic.c
index 8653fb0..4893a32 100644
--- a/zic.c
+++ b/zic.c
@@ -2456,6 +2456,8 @@ rule_cmp(struct rule const *a, struct rule const *b)
 		return 1;
 	if (a->r_hiyear != b->r_hiyear)
 		return a->r_hiyear < b->r_hiyear ? -1 : 1;
+	if (a->r_hiyear == ZIC_MAX)
+		return 0;
 	if (a->r_month - b->r_month != 0)
 		return a->r_month - b->r_month;
 	return a->r_dayofmonth - b->r_dayofmonth;
@@ -2469,12 +2471,16 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	register struct rule *		stdrp;
 	register struct rule *		dstrp;
 	register ptrdiff_t		i;
-	register const char *		abbrvar;
 	register int			compat = 0;
 	register int			c;
 	size_t				len;
 	int				offsetlen;
 	struct rule			stdr, dstr;
+	int dstcmp;
+	struct rule *lastrp[2] = { NULL, NULL };
+	struct zone zstr[2];
+	struct zone const *stdzp;
+	struct zone const *dstzp;
 
 	result[0] = '\0';
 
@@ -2484,63 +2490,64 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	  return -1;
 
 	zp = zpfirst + zonecount - 1;
-	stdrp = dstrp = NULL;
 	for (i = 0; i < zp->z_nrules; ++i) {
+		struct rule **last;
+		int cmp;
 		rp = &zp->z_rules[i];
-		if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX)
-			continue;
-		if (!rp->r_isdst) {
-			if (stdrp == NULL)
-				stdrp = rp;
-			else	return -1;
-		} else {
-			if (dstrp == NULL)
-				dstrp = rp;
-			else	return -1;
-		}
-	}
-	if (stdrp == NULL && dstrp == NULL) {
-		/*
-		** There are no rules running through "max".
-		** Find the latest std rule in stdabbrrp
-		** and latest rule of any type in stdrp.
-		*/
-		register struct rule *stdabbrrp = NULL;
-		for (i = 0; i < zp->z_nrules; ++i) {
-			rp = &zp->z_rules[i];
-			if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0)
-				stdabbrrp = rp;
-			if (rule_cmp(stdrp, rp) < 0)
-				stdrp = rp;
-		}
-		if (stdrp != NULL && stdrp->r_isdst) {
-			/* Perpetual DST.  */
-			dstr.r_month = TM_JANUARY;
-			dstr.r_dycode = DC_DOM;
-			dstr.r_dayofmonth = 1;
-			dstr.r_tod = 0;
-			dstr.r_todisstd = dstr.r_todisut = false;
-			dstr.r_isdst = stdrp->r_isdst;
-			dstr.r_save = stdrp->r_save;
-			dstr.r_abbrvar = stdrp->r_abbrvar;
-			stdr.r_month = TM_DECEMBER;
-			stdr.r_dycode = DC_DOM;
-			stdr.r_dayofmonth = 31;
-			stdr.r_tod = SECSPERDAY + stdrp->r_save;
-			stdr.r_todisstd = stdr.r_todisut = false;
-			stdr.r_isdst = false;
-			stdr.r_save = 0;
-			stdr.r_abbrvar
-			  = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
-			dstrp = &dstr;
-			stdrp = &stdr;
-		}
-	}
-	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst))
-		return -1;
-	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
-	len = doabbr(result, zp, abbrvar, false, 0, true);
-	offsetlen = stringoffset(result + len, - zp->z_stdoff);
+		last = &lastrp[rp->r_isdst];
+		cmp = rule_cmp(*last, rp);
+		if (cmp < 0)
+		  *last = rp;
+		else if (cmp == 0)
+		  return -1;
+	}
+	stdrp = lastrp[false];
+	dstrp = lastrp[true];
+	dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1;
+	stdzp = dstzp = zp;
+
+	if (dstcmp < 0) {
+	  /* Standard time all year.  */
+	  dstrp = NULL;
+	} else if (0 < dstcmp) {
+	  /* DST all year.  Use an abbreviation like
+	     "XXX3EDT4,0/0,J365/23" for EDT (-04) all year.  */
+	  zic_t save = dstrp ? dstrp->r_save : zp->z_save;
+	  if (0 <= save)
+	    {
+	      /* Positive DST, the typical case for all-year DST.
+		 Fake a timezone with negative DST.  */
+	      stdzp = &zstr[0];
+	      dstzp = &zstr[1];
+	      zstr[0].z_stdoff = zp->z_stdoff - 2 * save;
+	      zstr[0].z_format = "XXX";  /* Any 3 letters will do.  */
+	      zstr[0].z_format_specifier = 0;
+	      zstr[1].z_stdoff = zstr[0].z_stdoff;
+	      zstr[1].z_format = zp->z_format;
+	      zstr[1].z_format_specifier = zp->z_format_specifier;
+	    }
+	  dstr.r_month = TM_JANUARY;
+	  dstr.r_dycode = DC_DOM;
+	  dstr.r_dayofmonth = 1;
+	  dstr.r_tod = 0;
+	  dstr.r_todisstd = dstr.r_todisut = false;
+	  dstr.r_isdst = true;
+	  dstr.r_save = save < 0 ? save : -save;
+	  dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL;
+	  stdr.r_month = TM_DECEMBER;
+	  stdr.r_dycode = DC_DOM;
+	  stdr.r_dayofmonth = 31;
+	  stdr.r_tod = SECSPERDAY + dstr.r_save;
+	  stdr.r_todisstd = stdr.r_todisut = false;
+	  stdr.r_isdst = false;
+	  stdr.r_save = 0;
+	  stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL;
+	  dstrp = &dstr;
+	  stdrp = &stdr;
+	}
+	len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL,
+		     false, 0, true);
+	offsetlen = stringoffset(result + len, - stdzp->z_stdoff);
 	if (! offsetlen) {
 		result[0] = '\0';
 		return -1;
@@ -2548,11 +2555,11 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	len += offsetlen;
 	if (dstrp == NULL)
 		return compat;
-	len += doabbr(result + len, zp, dstrp->r_abbrvar,
+	len += doabbr(result + len, dstzp, dstrp->r_abbrvar,
 		      dstrp->r_isdst, dstrp->r_save, true);
 	if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) {
 	  offsetlen = stringoffset(result + len,
-				   - (zp->z_stdoff + dstrp->r_save));
+				   - (dstzp->z_stdoff + dstrp->r_save));
 	  if (! offsetlen) {
 	    result[0] = '\0';
 	    return -1;
@@ -2560,7 +2567,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	  len += offsetlen;
 	}
 	result[len++] = ',';
-	c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
@@ -2569,7 +2576,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		compat = c;
 	len += strlen(result + len);
 	result[len++] = ',';
-	c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
-- 
2.27.0



More information about the tz mailing list