From 6a4024dc43a99c7d5c4c59432f9b665d1e8e5f6f Mon Sep 17 00:00:00 2001 From: millert Date: Wed, 18 Sep 2024 17:05:50 +0000 Subject: [PATCH] zic: cherrypick support for %z in time zone formats This extends the zic input format to add support for %z, which expands to a UTC offset in as-short-as-possible ISO 8601 format. It's intended to better support zones that do not have an established abbreviation already. tzdata2024b and higher require a version of zic that supports the %z format. From upstream tzcode. OK beck@ --- usr.sbin/zic/zic.8 | 17 ++++++++- usr.sbin/zic/zic.c | 91 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 21 deletions(-) diff --git a/usr.sbin/zic/zic.8 b/usr.sbin/zic/zic.8 index 0ed93b54155..4b0ebdf2391 100644 --- a/usr.sbin/zic/zic.8 +++ b/usr.sbin/zic/zic.8 @@ -1,5 +1,5 @@ -.\" $OpenBSD: zic.8,v 1.5 2022/03/31 17:27:32 naddy Exp $ -.Dd $Mdocdate: March 31 2022 $ +.\" $OpenBSD: zic.8,v 1.6 2024/09/18 17:05:50 millert Exp $ +.Dd $Mdocdate: September 18 2024 $ .Dt ZIC 8 .Os .Sh NAME @@ -240,6 +240,19 @@ The pair of characters is used to show where the .Dq variable part of the time zone abbreviation goes. +Alternately, a format can use the pair of characters +.Em %z +to stand for the UTC offset in the form +.No \(+- Ns Em hh , +.No \(+- Ns Em hhmm , +or +.No \(+- Ns Em hhmmss , +using the shortest form that does not lose information, where +.Em hh , +.Em mm , +and +.Em ss +are the hours, minutes, and seconds east (+) or west (\(mi) of UTC. Alternately, a slash .Pq \&/ diff --git a/usr.sbin/zic/zic.c b/usr.sbin/zic/zic.c index 420b75f8225..2f94cd73777 100644 --- a/usr.sbin/zic/zic.c +++ b/usr.sbin/zic/zic.c @@ -1,4 +1,4 @@ -/* $OpenBSD: zic.c,v 1.26 2020/10/13 00:18:46 deraadt Exp $ */ +/* $OpenBSD: zic.c,v 1.27 2024/09/18 17:05:50 millert Exp $ */ /* ** This file is in the public domain, so clarified as of ** 2006-07-17 by Arthur David Olson. @@ -88,6 +88,7 @@ struct zone { long z_gmtoff; const char *z_rule; const char *z_format; + char z_format_specifier; long z_stdoff; @@ -107,7 +108,7 @@ static void associate(void); static void convert(long val, char *buf); static void convert64(zic_t val, char *buf); static void dolink(const char *fromfield, const char *tofield); -static void doabbr(char *abbr, size_t size, const char *format, +static void doabbr(char *abbr, size_t size, struct zone const *zp, const char *letters, int isdst, int doquotes); static void eat(const char *name, int num); static void eats(const char *name, int num, const char *rname, int rnum); @@ -148,6 +149,9 @@ static void writezone(const char *name, const char *string); extern char *__progname; +/* Bound on length of what %z can expand to. */ +enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 }; + static int charcnt; static int errors; static const char *filename; @@ -156,7 +160,7 @@ static int leapseen; static int leapminyear; static int leapmaxyear; static int linenum; -static int max_abbrvar_len; +static int max_abbrvar_len = PERCENT_Z_LEN_BOUND; static int max_format_len; static zic_t max_time; static int max_year; @@ -739,7 +743,7 @@ associate(void) ** Note, though, that if there's no rule, ** a '%s' in the format is a bad thing. */ - if (strchr(zp->z_format, '%') != 0) + if (zp->z_format_specifier == 's') error("%s in ruleless zone"); } } @@ -957,6 +961,7 @@ static int inzsub(char **fields, int nfields, int iscont) { char *cp; + char *cp1; static struct zone z; int i_gmtoff, i_rule, i_format; int i_untilyear, i_untilmonth; @@ -986,13 +991,22 @@ inzsub(char **fields, int nfields, int iscont) z.z_linenum = linenum; z.z_gmtoff = gethms(fields[i_gmtoff], "invalid UTC offset", TRUE); if ((cp = strchr(fields[i_format], '%')) != 0) { - if (*++cp != 's' || strchr(cp, '%') != 0) { + if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%') + || strchr(fields[i_format], '/')) { error("invalid abbreviation format"); return FALSE; } } z.z_rule = ecpyalloc(fields[i_rule]); - z.z_format = ecpyalloc(fields[i_format]); + z.z_format = cp1 = ecpyalloc(fields[i_format]); + z.z_format_specifier = cp ? *cp : '\0'; + if (z.z_format_specifier == 'z') { + if (noise) { + warning("format '%%z' not handled by pre-2015 versions " + "of zic"); + } + cp1[cp - fields[i_format]] = 's'; + } if (max_format_len < strlen(z.z_format)) max_format_len = strlen(z.z_format); hasuntil = nfields > i_untilyear; @@ -1635,19 +1649,58 @@ writezone(const char *name, const char *string) errx(1, "Error writing %s", fullname); } +static char const * +abbroffset(char *buf, zic_t offset) +{ + char sign = '+'; + int seconds, minutes; + + if (offset < 0) { + offset = -offset; + sign = '-'; + } + + seconds = offset % SECSPERMIN; + offset /= SECSPERMIN; + minutes = offset % MINSPERHOUR; + offset /= MINSPERHOUR; + if (100 <= offset) { + error("%%z UTC offset magnitude exceeds 99:59:59"); + return "%z"; + } else { + char *p = buf; + *p++ = sign; + *p++ = '0' + offset / 10; + *p++ = '0' + offset % 10; + if (minutes | seconds) { + *p++ = '0' + minutes / 10; + *p++ = '0' + minutes % 10; + if (seconds) { + *p++ = '0' + seconds / 10; + *p++ = '0' + seconds % 10; + } + } + *p = '\0'; + return buf; + } +} + static void -doabbr(char *abbr, size_t size, const char *format, const char *letters, +doabbr(char *abbr, size_t size, struct zone const *zp, const char *letters, int isdst, int doquotes) { char *cp, *slashp; - int len; + size_t len; + char const *format = zp->z_format; slashp = strchr(format, '/'); if (slashp == NULL) { - if (letters == NULL) - strlcpy(abbr, format, size); - else - snprintf(abbr, size, format, letters); + char letterbuf[PERCENT_Z_LEN_BOUND + 1]; + if (zp->z_format_specifier == 'z') + letters = abbroffset(letterbuf, -zp->z_gmtoff); + else if (letters == NULL) + letters = "%s"; + snprintf(abbr, size, format, letters); } else if (isdst) { strlcpy(abbr, slashp + 1, size); } else { @@ -1822,7 +1875,7 @@ stringzone(char *result, size_t size, const struct zone *zpfirst, int zonecount) if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0)) return; abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar; - doabbr(result, size, zp->z_format, abbrvar, FALSE, TRUE); + doabbr(result, size, zp, abbrvar, FALSE, TRUE); ep = end(result, size); if (stringoffset(ep, size - (ep - result), -zp->z_gmtoff) != 0) { result[0] = '\0'; @@ -1831,7 +1884,7 @@ stringzone(char *result, size_t size, const struct zone *zpfirst, int zonecount) if (dstrp == NULL) return; ep = end(result, size); - doabbr(ep, size - (ep - result), zp->z_format, dstrp->r_abbrvar, TRUE, TRUE); + doabbr(ep, size - (ep - result), zp, dstrp->r_abbrvar, TRUE, TRUE); if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) { ep = end(result, size); if (stringoffset(ep, size - (ep - result), @@ -1957,8 +2010,8 @@ outzone(const struct zone *zpfirst, int zonecount) startoff = zp->z_gmtoff; if (zp->z_nrules == 0) { stdoff = zp->z_stdoff; - doabbr(startbuf, max_abbr_len + 1, zp->z_format, - NULL, stdoff != 0, FALSE); + doabbr(startbuf, max_abbr_len + 1, zp, NULL, + stdoff != 0, FALSE); type = addtype(oadd(zp->z_gmtoff, stdoff), startbuf, stdoff != 0, startttisstd, startttisgmt); @@ -2041,7 +2094,7 @@ outzone(const struct zone *zpfirst, int zonecount) stdoff); doabbr(startbuf, max_abbr_len + 1, - zp->z_format, + zp, rp->r_abbrvar, rp->r_stdoff != 0, FALSE); @@ -2052,7 +2105,7 @@ outzone(const struct zone *zpfirst, int zonecount) stdoff)) { doabbr(startbuf, max_abbr_len + 1, - zp->z_format, + zp, rp->r_abbrvar, rp->r_stdoff != 0, FALSE); @@ -2060,7 +2113,7 @@ outzone(const struct zone *zpfirst, int zonecount) } eats(zp->z_filename, zp->z_linenum, rp->r_filename, rp->r_linenum); - doabbr(ab, max_abbr_len + 1, zp->z_format, + doabbr(ab, max_abbr_len + 1, zp, rp->r_abbrvar, rp->r_stdoff != 0, FALSE); offset = oadd(zp->z_gmtoff, rp->r_stdoff); type = addtype(offset, ab, rp->r_stdoff != 0, -- 2.20.1