[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