From ca350847bdb7229d069572879fdf2c66ad937b31 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 8 Apr 2018 19:08:51 -0700
Subject: [PATCH] Support s and d suffixes in Zone SAVE column too

Lack of regularity noted by Michael Deckers in:
https://mm.icann.org/pipermail/tz/2018-April/026408.html
* NEWS, zic.8: Document this.
* zic.c (struct zone.z_rule): No longer pointer-to-const.
(struct zone.z_isdst): New member.
(getstdoff): New function, mostly taken from part of inrule.
(associate, inrule): Use it.
(stringzone, outzone): Look at z_isdst, not at whether z_stdoff is
nonzero.
---
 NEWS  |  4 ++--
 zic.8 |  4 +++-
 zic.c | 42 ++++++++++++++++++++++++++----------------
 3 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/NEWS b/NEWS
index 13197f5..5f628f4 100644
--- a/NEWS
+++ b/NEWS
@@ -4,11 +4,11 @@ Unreleased, experimental changes
 
   Briefly:
 
-    New 's' and 'd' suffixes in SAVE columns of Rule lines.
+    New 's' and 'd' suffixes in SAVE columns of Rule and Zone lines.
 
   Changes to data format and to code
 
-    The SAVE column in Rule lines can now be followed by an 's' or 'd'
+    The SAVE column of Rule and Zone lines can now have an 's' or 'd'
     suffix, which specifies whether the adjusted time is standard time
     or daylight saving time.  If no suffix is given, daylight saving
     time is used if and only if the SAVE column is nonzero; this is
diff --git a/zic.8 b/zic.8
index 97644ac..d105b24 100644
--- a/zic.8
+++ b/zic.8
@@ -372,7 +372,9 @@ begin the field with a minus sign if time must be subtracted from UT.
 .TP
 .B RULES
 The name of the rules that apply in the time zone or,
-alternatively, an amount of time to add to local standard time.
+alternatively, a field in the same format as a rule-line SAVE column,
+giving of the amount of time to be added to local standard time
+effect, and whether the resulting time is standard or daylight saving.
 If this field is
 .B \*-
 then standard time always applies in the time zone.
diff --git a/zic.c b/zic.c
index d271bfc..90ae1f4 100644
--- a/zic.c
+++ b/zic.c
@@ -112,10 +112,11 @@ struct zone {
 
 	const char *	z_name;
 	zic_t		z_gmtoff;
-	const char *	z_rule;
+	char *		z_rule;
 	const char *	z_format;
 	char		z_format_specifier;
 
+	bool		z_isdst;
 	zic_t		z_stdoff;
 
 	struct rule *	z_rules;
@@ -155,6 +156,7 @@ static void	dolink(const char *, const char *, bool);
 static char **	getfields(char * buf);
 static zic_t	gethms(const char * string, const char * errstring,
 		       bool);
+static zic_t	getstdoff(char *, bool *);
 static void	infile(const char * filename);
 static void	inleap(char ** fields, int nfields);
 static void	inlink(char ** fields, int nfields);
@@ -1090,8 +1092,7 @@ associate(void)
 			** Maybe we have a local standard time offset.
 			*/
 			eat(zp->z_filename, zp->z_linenum);
-			zp->z_stdoff = gethms(zp->z_rule, _("unruly zone"),
-				true);
+			zp->z_stdoff = getstdoff(zp->z_rule, &zp->z_isdst);
 			/*
 			** Note, though, that if there's no rule,
 			** a '%s' in the format is a bad thing.
@@ -1250,11 +1251,28 @@ warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
 		    sign * (mm * SECSPERMIN + ss));
 }
 
+static zic_t
+getstdoff(char *field, bool *isdst)
+{
+  int dst = -1;
+  zic_t stdoff;
+  size_t fieldlen = strlen(field);
+  if (fieldlen != 0) {
+    char *ep = field + fieldlen - 1;
+    switch (*ep) {
+      case 'd': dst = 1; *ep = '\0'; break;
+      case 's': dst = 0; *ep = '\0'; break;
+    }
+  }
+  stdoff = gethms(field, _("invalid saved time"), true);
+  *isdst = dst < 0 ? stdoff != 0 : dst;
+  return stdoff;
+}
+
 static void
 inrule(char **fields, int nfields)
 {
 	static struct rule	r;
-	int isdst = -1;
 
 	if (nfields != RULE_FIELDS) {
 		error(_("wrong number of fields on Rule line"));
@@ -1266,15 +1284,7 @@ inrule(char **fields, int nfields)
 	}
 	r.r_filename = filename;
 	r.r_linenum = linenum;
-	if (fields[RF_STDOFF][0]) {
-	  char *ep = fields[RF_STDOFF] + strlen(fields[RF_STDOFF]) - 1;
-	  switch (*ep) {
-	    case 'd': isdst = 1; *ep = '\0'; break;
-	    case 's': isdst = 0; *ep = '\0'; break;
-	  }
-	}
-	r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), true);
-	r.r_isdst = isdst < 0 ? r.r_stdoff != 0 : isdst;
+	r.r_stdoff = getstdoff(fields[RF_STDOFF], &r.r_isdst);
 	rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
 		fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
 	r.r_name = ecpyalloc(fields[RF_NAME]);
@@ -2360,7 +2370,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 			stdrp = &stdr;
 		}
 	}
-	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))
+	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);
@@ -2549,9 +2559,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		startoff = zp->z_gmtoff;
 		if (zp->z_nrules == 0) {
 			stdoff = zp->z_stdoff;
-			doabbr(startbuf, zp, NULL, stdoff != 0, stdoff, false);
+			doabbr(startbuf, zp, NULL, zp->z_isdst, stdoff, false);
 			type = addtype(oadd(zp->z_gmtoff, stdoff),
-				startbuf, stdoff != 0, startttisstd,
+				startbuf, zp->z_isdst, startttisstd,
 				startttisgmt);
 			if (usestart) {
 				addtt(starttime, type);
-- 
2.7.4

