<div dir="ltr">I&#39;ve implemented support for this in Noda Time (but it&#39;s easily removed if we decide not to go ahead with it). What&#39;s the normal lead time for a change like this? The commit message states:<div><br></div><div>&gt; <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&#39;s just saying that you haven&#39;t changed the data <i>yet</i>. At some point, we&#39;re expecting the data to take advantage of this functionality, I assume? If so, do we have an expectation of when we&#39;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">&lt;<a href="mailto:eggert@cs.ucla.edu" target="_blank">eggert@cs.ucla.edu</a>&gt;</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&#39;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 &#39;%z&#39; in a zone format now stand for the UTC<br>
+    offset, e.g., &#39;-07&#39; for seven hours behind UTC and &#39;+0530&#39; 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 &quot;variable part&quot;<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 &quot;+995959&quot; - 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&#39;s no rule,<br>
                        ** a &#39;%s&#39; in the format is a bad thing.<br>
                        */<br>
-                       if (strchr(zp-&gt;z_format, &#39;%&#39;) != 0)<br>
+                       if (zp-&gt;z_format_specifier == &#39;s&#39;)<br>
                                error(&quot;%s&quot;, _(&quot;%s in ruleless zone&quot;));<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], _(&quot;invalid UT offset&quot;), true);<br>
        if ((cp = strchr(fields[i_format], &#39;%&#39;)) != 0) {<br>
-               if (*++cp != &#39;s&#39; || strchr(cp, &#39;%&#39;) != 0) {<br>
+               if ((*++cp != &#39;s&#39; &amp;&amp; *cp != &#39;z&#39;) || strchr(cp, &#39;%&#39;)<br>
+                   || strchr(fields[i_format], &#39;/&#39;)) {<br>
                        error(_(&quot;invalid abbreviation format&quot;));<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 : &#39;\0&#39;;<br>
+       if (z.z_format_specifier == &#39;z&#39;) {<br>
+         if (noise)<br>
+           warning(_(&quot;format &#39;%s&#39; not handled by pre-2015 versions of zic&quot;),<br>
+                   z.z_format);<br>
+         cp1[cp - fields[i_format]] = &#39;s&#39;;<br>
+       }<br>
        if (max_format_len &lt; strlen(z.z_format))<br>
                max_format_len = strlen(z.z_format);<br>
        hasuntil = nfields &gt; 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 = &#39;+&#39;;<br>
+  int seconds, minutes;<br>
+<br>
+  if (offset &lt; 0) {<br>
+    offset = -offset;<br>
+    sign = &#39;-&#39;;<br>
+  }<br>
+<br>
+  seconds = offset % SECSPERMIN;<br>
+  offset /= SECSPERMIN;<br>
+  minutes = offset % MINSPERHOUR;<br>
+  offset /= MINSPERHOUR;<br>
+  if (100 &lt;= offset) {<br>
+    error(_(&quot;%%z UTC offset magnitude exceeds 99:59:59&quot;));<br>
+    return &quot;%z&quot;;<br>
+  } else {<br>
+    char *p = buf;<br>
+    *p++ = sign;<br>
+    *p++ = &#39;0&#39; + offset / 10;<br>
+    *p++ = &#39;0&#39; + offset % 10;<br>
+    if (minutes | seconds) {<br>
+      *p++ = &#39;0&#39; + minutes / 10;<br>
+      *p++ = &#39;0&#39; + minutes % 10;<br>
+      if (seconds) {<br>
+       *p++ = &#39;0&#39; + seconds / 10;<br>
+       *p++ = &#39;0&#39; + seconds % 10;<br>
+      }<br>
+    }<br>
+    *p = &#39;\0&#39;;<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-&gt;z_format;<br>
<br>
        slashp = strchr(format, &#39;/&#39;);<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-&gt;z_format_specifier == &#39;z&#39;)<br>
+           letters = abbroffset(letterbuf, -zp-&gt;z_gmtoff);<br>
+         else if (!letters)<br>
+           letters = &quot;%s&quot;;<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 &amp;&amp; (zp-&gt;z_nrules != 0 || zp-&gt;z_stdoff != 0))<br>
                return -1;<br>
        abbrvar = (stdrp == NULL) ? &quot;&quot; : stdrp-&gt;r_abbrvar;<br>
-       len = doabbr(result, zp-&gt;z_format, abbrvar, false, true);<br>
+       len = doabbr(result, zp, abbrvar, false, true);<br>
        offsetlen = stringoffset(result + len, -zp-&gt;z_gmtoff);<br>
        if (! offsetlen) {<br>
                result[0] = &#39;\0&#39;;<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-&gt;z_format, dstrp-&gt;r_abbrvar, true, true);<br>
+       len += doabbr(result + len, zp, dstrp-&gt;r_abbrvar, true, true);<br>
        if (dstrp-&gt;r_stdoff != SECSPERMIN * MINSPERHOUR) {<br>
          offsetlen = stringoffset(result + len,<br>
                                   -(zp-&gt;z_gmtoff + dstrp-&gt;r_stdoff));<br>
@@ -2307,8 +2360,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                startoff = zp-&gt;z_gmtoff;<br>
                if (zp-&gt;z_nrules == 0) {<br>
                        stdoff = zp-&gt;z_stdoff;<br>
-                       doabbr(startbuf, zp-&gt;z_format,<br>
-                              NULL, stdoff != 0, false);<br>
+                       doabbr(startbuf, zp, NULL, stdoff != 0, false);<br>
                        type = addtype(oadd(zp-&gt;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 &lt; starttime) {<br>
                                                startoff = oadd(zp-&gt;z_gmtoff,<br>
                                                        stdoff);<br>
-                                               doabbr(startbuf, zp-&gt;z_format,<br>
+                                               doabbr(startbuf, zp,<br>
                                                        rp-&gt;r_abbrvar,<br>
                                                        rp-&gt;r_stdoff != 0,<br>
                                                        false);<br>
@@ -2410,7 +2462,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                                                startoff == oadd(zp-&gt;z_gmtoff,<br>
                                                stdoff)) {<br>
                                                        doabbr(startbuf,<br>
-                                                               zp-&gt;z_format,<br>
+                                                               zp,<br>
                                                                rp-&gt;r_abbrvar,<br>
                                                                rp-&gt;r_stdoff !=<br>
                                                                false,<br>
@@ -2419,7 +2471,7 @@ outzone(const struct zone * const zpfirst, const int zonecount)<br>
                                }<br>
                                eats(zp-&gt;z_filename, zp-&gt;z_linenum,<br>
                                        rp-&gt;r_filename, rp-&gt;r_linenum);<br>
-                               doabbr(ab, zp-&gt;z_format, rp-&gt;r_abbrvar,<br>
+                               doabbr(ab, zp, rp-&gt;r_abbrvar,<br>
                                        rp-&gt;r_stdoff != 0, false);<br>
                                offset = oadd(zp-&gt;z_gmtoff, rp-&gt;r_stdoff);<br>
                                type = addtype(offset, ab, rp-&gt;r_stdoff != 0,<br>
<span class="HOEnZb"><font color="#888888">--<br>
2.1.4<br>
<br>
</font></span></blockquote></div><br></div>