<div dir="ltr">I've implemented support for this in Noda Time (but it's easily removed if we decide not to go ahead with it). What's the normal lead time for a change like this? The commit message states:<div><br></div><div>> <span style="font-size:12.8000001907349px"> </span><span style="font-size:12.8000001907349px">This is a change only to zic; </span><span style="font-size:12.8000001907349px">it does not affect the data, which can still be compiled with </span><span style="font-size:12.8000001907349px">older zic versions.</span></div><div><span style="font-size:12.8000001907349px"><br></span></div><div><span style="font-size:12.8000001907349px">... but presumably that's just saying that you haven't changed the data <i>yet</i>. At some point, we're expecting the data to take advantage of this functionality, I assume? If so, do we have an expectation of when we'd first do so?</span></div><div><span style="font-size:12.8000001907349px"><br></span></div><div><span style="font-size:12.8000001907349px">Jon</span></div><div><span style="font-size:12.8000001907349px"><br></span></div></div><div class="gmail_extra"><br><div class="gmail_quote">On 20 July 2015 at 04:23, Paul Eggert <span dir="ltr"><<a href="mailto:eggert@cs.ucla.edu" target="_blank">eggert@cs.ucla.edu</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">This extends the zic input format to add support for %z, which<br>
expands to a UTC offset in as-short-as-possible ISO 8601 format.<br>
It's intended to better support zones that do not have an<br>
established abbreviation already.  This is a change only to zic;<br>
it does not affect the data, which can still be compiled with<br>
older zic versions.<br>
* NEWS, zic.8: Document this.<br>
* zic.c (struct zone): New member z_format_specifier.<br>
(PERCENT_Z_LEN_BOUND): New constant.<br>
(max_abbrvar_len): Initialize to it, rather than to 0.<br>
(associate, inzsub, doabbr): Add support for %z.<br>
(abbroffset): New function.<br>
(doabbr): 2nd arg is now struct zone *, not a char *.<br>
All callers changed.<br>
---<br>
 NEWS  |  5 +++++<br>
 zic.8 | 13 +++++++++++<br>
 zic.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------<br>
 3 files changed, 85 insertions(+), 15 deletions(-)<br>
<br>
diff --git a/NEWS b/NEWS<br>
index b7593e7..9eff28c 100644<br>
--- a/NEWS<br>
+++ b/NEWS<br>
@@ -25,6 +25,11 @@ Unreleased, experimental changes<br>
     (Thanks to Jon Skeet and Arthur David Olson.)  Constraints on<br>
     simultaneity are now documented.<br>
<br>
+    The two characters '%z' in a zone format now stand for the UTC<br>
+    offset, e.g., '-07' for seven hours behind UTC and '+0530' for<br>
+    five hours and thirty minutes ahead.  This better supports time<br>
+    zone abbreviations conforming to POSIX.1-2001 and later.<br>
+<br>
   Changes affecting installed data files<br>
<br>
     Data entries have been simplified for Atlantic/Canary, Europe/Simferopol,<br>
diff --git a/zic.8 b/zic.8<br>
index dfb01db..94b6753 100644<br>
--- a/zic.8<br>
+++ b/zic.8<br>
@@ -350,6 +350,19 @@ The pair of characters<br>
 is used to show where the<br>
 .q "variable part"<br>
 of the time zone abbreviation goes.<br>
+Alternately, a format can use the pair of characters<br>
+.B %z<br>
+to stand for the UTC offset in the form<br>
+.RI \(+- hh ,<br>
+.RI \(+- hhmm ,<br>
+or<br>
+.RI \(+- hhmmss ,<br>
+using the shortest form that does not lose information, where<br>
+.IR hh ,<br>
+.IR mm ,<br>
+and<br>
+.I ss<br>
+are the hours, minutes, and seconds east (+) or west (\(mi) of UTC.<br>
 Alternately,<br>
 a slash (/)<br>
 separates standard and daylight abbreviations.<br>
diff --git a/zic.c b/zic.c<br>
index 6854033..3c4ae93 100644<br>
--- a/zic.c<br>
+++ b/zic.c<br>
@@ -76,6 +76,7 @@ struct zone {<br>
        zic_t           z_gmtoff;<br>
        const char *    z_rule;<br>
        const char *    z_format;<br>
+       char            z_format_specifier;<br>
<br>
        zic_t           z_stdoff;<br>
<br>
@@ -130,6 +131,9 @@ static void rulesub(struct rule * rp,<br>
 static zic_t   tadd(zic_t t1, zic_t t2);<br>
 static bool    yearistype(int year, const char * type);<br>
<br>
+/* Bound on length of what %z can expand to.  */<br>
+enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 };<br>
+<br>
 static int             charcnt;<br>
 static bool            errors;<br>
 static bool            warnings;<br>
@@ -139,7 +143,7 @@ static bool         leapseen;<br>
 static zic_t           leapminyear;<br>
 static zic_t           leapmaxyear;<br>
 static int             linenum;<br>
-static int             max_abbrvar_len;<br>
+static int             max_abbrvar_len = PERCENT_Z_LEN_BOUND;<br>
 static int             max_format_len;<br>
 static zic_t           max_year;<br>
 static zic_t           min_year;<br>
@@ -955,7 +959,7 @@ associate(void)<br>
                        ** Note, though, that if there's no rule,<br>
                        ** a '%s' in the format is a bad thing.<br>
                        */<br>
-                       if (strchr(zp->z_format, '%') != 0)<br>
+                       if (zp->z_format_specifier == 's')<br>
                                error("%s", _("%s in ruleless zone"));<br>
                }<br>
        }<br>
@@ -1170,6 +1174,7 @@ static bool<br>
 inzsub(char **fields, int nfields, bool iscont)<br>
 {<br>
        register char *         cp;<br>
+       char *                  cp1;<br>
        static struct zone      z;<br>
        register int            i_gmtoff, i_rule, i_format;<br>
        register int            i_untilyear, i_untilmonth;<br>
@@ -1201,13 +1206,21 @@ inzsub(char **fields, int nfields, bool iscont)<br>
        z.z_linenum = linenum;<br>
        z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true);<br>
        if ((cp = strchr(fields[i_format], '%')) != 0) {<br>
-               if (*++cp != 's' || strchr(cp, '%') != 0) {<br>
+               if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')<br>
+                   || strchr(fields[i_format], '/')) {<br>
                        error(_("invalid abbreviation format"));<br>
                        return false;<br>
                }<br>
        }<br>
        z.z_rule = ecpyalloc(fields[i_rule]);<br>
-       z.z_format = ecpyalloc(fields[i_format]);<br>
+       z.z_format = cp1 = ecpyalloc(fields[i_format]);<br>
+       z.z_format_specifier = cp ? *cp : '\0';<br>
+       if (z.z_format_specifier == 'z') {<br>
+         if (noise)<br>
+           warning(_("format '%s' not handled by pre-2015 versions of zic"),<br>
+                   z.z_format);<br>
+         cp1[cp - fields[i_format]] = 's';<br>
+       }<br>
        if (max_format_len < strlen(z.z_format))<br>
                max_format_len = strlen(z.z_format);<br>
        hasuntil = nfields > i_untilyear;<br>
@@ -1890,19 +1903,59 @@ writezone(const char *const name, const char *const string, char version)<br>
        free(fullname);<br>
 }<br>
<br>
+static char const *<br>
+abbroffset(char *buf, zic_t offset)<br>
+{<br>
+  char sign = '+';<br>
+  int seconds, minutes;<br>
+<br>
+  if (offset < 0) {<br>
+    offset = -offset;<br>
+    sign = '-';<br>
+  }<br>
+<br>
+  seconds = offset % SECSPERMIN;<br>
+  offset /= SECSPERMIN;<br>
+  minutes = offset % MINSPERHOUR;<br>
+  offset /= MINSPERHOUR;<br>
+  if (100 <= offset) {<br>
+    error(_("%%z UTC offset magnitude exceeds 99:59:59"));<br>
+    return "%z";<br>
+  } else {<br>
+    char *p = buf;<br>
+    *p++ = sign;<br>
+    *p++ = '0' + offset / 10;<br>
+    *p++ = '0' + offset % 10;<br>
+    if (minutes | seconds) {<br>
+      *p++ = '0' + minutes / 10;<br>
+      *p++ = '0' + minutes % 10;<br>
+      if (seconds) {<br>
+       *p++ = '0' + seconds / 10;<br>
+       *p++ = '0' + seconds % 10;<br>
+      }<br>
+    }<br>
+    *p = '\0';<br>
+    return buf;<br>
+  }<br>
+}<br>
+<br>
 static size_t<br>
-doabbr(char *const abbr, const char *const format, const char *const letters,<br>
+doabbr(char *abbr, struct zone const *zp, char const *letters,<br>
        bool isdst, bool doquotes)<br>
 {<br>
        register char * cp;<br>
        register char * slashp;<br>
        register size_t len;<br>
+       char const *format = zp->z_format;<br>
<br>
        slashp = strchr(format, '/');<br>
        if (slashp == NULL) {<br>
-               if (letters == NULL)<br>
-                       strcpy(abbr, format);<br>
-               else    sprintf(abbr, format, letters);<br>
+         char letterbuf[PERCENT_Z_LEN_BOUND + 1];<br>
+         if (zp->z_format_specifier == 'z')<br>
+           letters = abbroffset(letterbuf, -zp->z_gmtoff);<br>
+         else if (!letters)<br>
+           letters = "%s";<br>
+         sprintf(abbr, format, letters);<br>
        } else if (isdst) {<br>
                strcpy(abbr, slashp + 1);<br>
        } else {<br>
@@ -2127,7 +2180,7 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)<br>
        if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))<br>
                return -1;<br>
        abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;<br>
-       len = doabbr(result, zp->z_format, abbrvar, false, true);<br>
+       len = doabbr(result, zp, abbrvar, false, true);<br>
        offsetlen = stringoffset(result + len, -zp->z_gmtoff);<br>
        if (! offsetlen) {<br>
                result[0] = '\0';<br>
@@ -2136,7 +2189,7 @@ stringzone(char *result, const struct zone *const zpfirst, const int zonecount)<br>
        len += offsetlen;<br>
        if (dstrp == NULL)<br>
                return compat;<br>
-       len += doabbr(result + len, zp->z_format, dstrp->r_abbrvar, true, true);<br>
+       len += doabbr(result + len, zp, dstrp->r_abbrvar, true, true);<br>
        if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) {<br>
          offsetlen = stringoffset(result + len,<br>
                                   -(zp->z_gmtoff + dstrp->r_stdoff));<br>
@@ -2307,8 +2360,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                startoff = zp->z_gmtoff;<br>
                if (zp->z_nrules == 0) {<br>
                        stdoff = zp->z_stdoff;<br>
-                       doabbr(startbuf, zp->z_format,<br>
-                              NULL, stdoff != 0, false);<br>
+                       doabbr(startbuf, zp, NULL, stdoff != 0, false);<br>
                        type = addtype(oadd(zp->z_gmtoff, stdoff),<br>
                                startbuf, stdoff != 0, startttisstd,<br>
                                startttisgmt);<br>
@@ -2400,7 +2452,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                                        if (ktime < starttime) {<br>
                                                startoff = oadd(zp->z_gmtoff,<br>
                                                        stdoff);<br>
-                                               doabbr(startbuf, zp->z_format,<br>
+                                               doabbr(startbuf, zp,<br>
                                                        rp->r_abbrvar,<br>
                                                        rp->r_stdoff != 0,<br>
                                                        false);<br>
@@ -2410,7 +2462,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                                                startoff == oadd(zp->z_gmtoff,<br>
                                                stdoff)) {<br>
                                                        doabbr(startbuf,<br>
-                                                               zp->z_format,<br>
+                                                               zp,<br>
                                                                rp->r_abbrvar,<br>
                                                                rp->r_stdoff !=<br>
                                                                false,<br>
@@ -2419,7 +2471,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                                }<br>
                                eats(zp->z_filename, zp->z_linenum,<br>
                                        rp->r_filename, rp->r_linenum);<br>
-                               doabbr(ab, zp->z_format, rp->r_abbrvar,<br>
+                               doabbr(ab, zp, rp->r_abbrvar,<br>
                                        rp->r_stdoff != 0, false);<br>
                                offset = oadd(zp->z_gmtoff, rp->r_stdoff);<br>
                                type = addtype(offset, ab, rp->r_stdoff != 0,<br>
<span class="HOEnZb"><font color="#888888">--<br>
2.1.4<br>
<br>
</font></span></blockquote></div><br></div>