Although there have been only 27 leap seconds so far and possibly
there will be no more, some TZif readers (e.g., glibc) do not have
the limit of 50 leap seconds hardwired into tzcode localtime.c.
Improve zic so that it can generate TZif files with more than 50
leap seconds to test these readers, just in case.
* NEWS, tzfile.h: Mention this.
* zic.c (leapcnt, strict timerange.leapbase)
(struct timerange.leapcount): Now ptrdiff_t, not int.
All uses changed.
(leap_alloc): New static var.
(trans, corr, roll): Remove these static vars, which have
an arbitrary limit on the number of leap seconds, and replace
them with ...
(leap): ... this new static var.  All uses changed.
(writezone): If -v, warn if the leap second count exceeds
the tzcode runtime limit.
(leapadd): Grow leap second table dynamically if needed, instead
of failing and exiting when a fixed-size table is too small.
---
 NEWS     |  8 ++++++
 tzfile.h |  6 ++--
 zic.c    | 85 +++++++++++++++++++++++++++++---------------------------
 3 files changed, 56 insertions(+), 43 deletions(-)

diff --git a/NEWS b/NEWS
index dd592871..e801faf1 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,7 @@ Unreleased, experimental changes
     The "right" TZif files are no longer installed by default.
     -DTZ_RUNTIME_LEAPS=0 disables runtime support for leap seconds.
     TZif files are no longer limited to 50 bytes of abbreviations.
+    zic is no longer limited to 50 leap seconds.
     Several integer overflow bugs have been fixed.
 
   Changes to build procedure
@@ -36,6 +37,13 @@ Unreleased, experimental changes
     already consuming 40 bytes.  zic -v warns if it generates a file
     that exceeds the old 50-byte limit.
 
+    zic -L can now generate TZif files with more than 50 leap seconds.
+    This helps test TZif readers not limited to 50 leap seconds, as
+    tzcode's localtime.c is; it has little immediate need for
+    practical timekeeping as there have been only 27 leap seconds and
+    possibly there will be no more, due to planned changes to UTC.
+    zic -v warns if its output exceeds the old 50-second limit.
+
     localtime.c no longer accesses the posixrules file generated by
     zic -p.  Hence for obsolete and nonconforming settings like
     TZ="AST4ADT" it now typically falls back on US DST rules, rather
diff --git a/tzfile.h b/tzfile.h
index 1b1bac4a..1941bc3b 100644
--- a/tzfile.h
+++ b/tzfile.h
@@ -89,7 +89,8 @@ struct tzhead {
 */
 
 #ifndef TZ_MAX_TIMES
-/* This must be at least 310 for Asia/Hebron with 'zic -b fat'.  */
+/* The following limit applies to localtime.c; zic has no such limit.
+   The limit must be at least 310 for Asia/Hebron with 'zic -b fat'.  */
 # define TZ_MAX_TIMES 2000
 #endif /* !defined TZ_MAX_TIMES */
 
@@ -105,7 +106,8 @@ struct tzhead {
 #endif /* !defined TZ_MAX_CHARS */
 
 #ifndef TZ_MAX_LEAPS
-/* This must be at least 27 for leap seconds from 1972 through mid-2023.
+/* The following limit applies to localtime.c; zic has no such limit.
+   The limit must be at least 27 for leap seconds from 1972 through mid-2023.
    There's a plan to discontinue leap seconds by 2035.  */
 # define TZ_MAX_LEAPS 50       /* Maximum number of leap second corrections */
 #endif /* !defined TZ_MAX_LEAPS */
diff --git a/zic.c b/zic.c
index 1a2f4741..87450745 100644
--- a/zic.c
+++ b/zic.c
@@ -298,7 +298,8 @@ static int          charcnt;
 static bool            errors;
 static bool            warnings;
 static int             filenum;
-static int             leapcnt;
+static ptrdiff_t       leapcnt;
+static ptrdiff_t       leap_alloc;
 static bool            leapseen;
 static zic_t           leapminyear;
 static zic_t           leapmaxyear;
@@ -539,9 +540,11 @@ static unsigned char       desigidx[TZ_MAX_TYPES];
 static bool            ttisstds[TZ_MAX_TYPES];
 static bool            ttisuts[TZ_MAX_TYPES];
 static char            chars[TZ_MAX_CHARS];
-static zic_t           trans[TZ_MAX_LEAPS];
-static zic_t           corr[TZ_MAX_LEAPS];
-static char            roll[TZ_MAX_LEAPS];
+static struct {
+  zic_t trans;
+  zic_t corr;
+  char roll;
+} *leap;
 
 /*
 ** Memory allocation.
@@ -2606,7 +2609,7 @@ atcomp(const void *avp, const void *bvp)
 struct timerange {
   int defaulttype;
   ptrdiff_t base, count;
-  int leapbase, leapcount;
+  ptrdiff_t leapbase, leapcount;
   bool leapexpiry;
 };
 
@@ -2626,13 +2629,13 @@ limitrange(struct timerange r, zic_t lo, zic_t hi,
      positive leap second if and only if it has a positive correction.
      This supports common TZif readers that assume that the first leap
      second is positive if and only if its correction is positive.  */
-  while (1 < r.leapcount && trans[r.leapbase + 1] <= lo) {
+  while (1 < r.leapcount && leap[r.leapbase + 1].trans <= lo) {
     r.leapcount--;
     r.leapbase++;
   }
   while (0 < r.leapbase
-        && ((corr[r.leapbase - 1] < corr[r.leapbase])
-            != (0 < corr[r.leapbase]))) {
+        && ((leap[r.leapbase - 1].corr < leap[r.leapbase].corr)
+            != (0 < leap[r.leapbase].corr))) {
     r.leapcount++;
     r.leapbase--;
   }
@@ -2642,7 +2645,7 @@ limitrange(struct timerange r, zic_t lo, zic_t hi,
   if (hi < max_time) {
     while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
       r.count--;
-    while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
+    while (0 < r.leapcount && hi + 1 < leap[r.leapbase + r.leapcount - 
1].trans)
       r.leapcount--;
   }
 
@@ -2713,14 +2716,19 @@ writezone(const char *const name, const char *const 
string, char version,
                timecnt = toi;
        }
 
-       if (noise && timecnt > 1200) {
-         if (timecnt > TZ_MAX_TIMES)
+       if (noise) {
+         if (1200 < timecnt) {
+           if (TZ_MAX_TIMES < timecnt)
                warning(_("reference clients mishandle"
                          " more than %d transition times"),
                        TZ_MAX_TIMES);
-         else
+           else
                warning(_("pre-2014 clients may mishandle"
                          " more than 1200 transition times"));
+         }
+         if (TZ_MAX_LEAPS < leapcnt)
+           warning(_("reference clients mishandle more than %d leap seconds"),
+                   TZ_MAX_LEAPS);
        }
        /*
        ** Transfer.
@@ -2736,8 +2744,8 @@ writezone(const char *const name, const char *const 
string, char version,
        for (i = 0; i < timecnt; ++i) {
                j = leapcnt;
                while (--j >= 0)
-                       if (ats[i] > trans[j] - corr[j]) {
-                               ats[i] = tadd(ats[i], corr[j]);
+                       if (leap[j].trans - leap[j].corr < ats[i]) {
+                               ats[i] = tadd(ats[i], leap[j].corr);
                                break;
                        }
        }
@@ -2766,7 +2774,7 @@ writezone(const char *const name, const char *const 
string, char version,
            version = '4';
          }
          if (0 < r->leapcount
-             && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) {
+             && leap[r->leapbase].corr != 1 && leap[r->leapbase].corr != -1) {
            if (noise)
              warning(_("%s: pre-2021b clients may mishandle"
                        " leap second table truncation"),
@@ -2781,7 +2789,7 @@ writezone(const char *const name, const char *const 
string, char version,
 
        for (pass = 1; pass <= 2; ++pass) {
                register ptrdiff_t thistimei, thistimecnt, thistimelim;
-               register int    thisleapi, thisleapcnt, thisleaplim;
+               register ptrdiff_t thisleapi, thisleapcnt, thisleaplim;
                struct tzhead tzh;
                int pretranstype = -1, thisdefaulttype;
                bool locut, hicut, thisleapexpiry;
@@ -3026,8 +3034,8 @@ writezone(const char *const name, const char *const 
string, char version,
                for (i = thisleapi; i < thisleaplim; ++i) {
                        register zic_t  todo;
 
-                       if (roll[i]) {
-                               if (timecnt == 0 || trans[i] < ats[0]) {
+                       if (leap[i].roll) {
+                               if (timecnt == 0 || leap[i].trans < ats[0]) {
                                        j = 0;
                                        while (isdsts[j])
                                                if (++j >= typecnt) {
@@ -3037,14 +3045,14 @@ writezone(const char *const name, const char *const 
string, char version,
                                } else {
                                        j = 1;
                                        while (j < timecnt &&
-                                               trans[i] >= ats[j])
+                                               ats[j] <= leap[i].trans)
                                                        ++j;
                                        j = types[j - 1];
                                }
-                               todo = tadd(trans[i], -utoffs[j]);
-                       } else  todo = trans[i];
+                               todo = tadd(leap[i].trans, -utoffs[j]);
+                       } else  todo = leap[i].trans;
                        puttzcodepass(todo, fp, pass);
-                       puttzcode(corr[i], fp);
+                       puttzcode(leap[i].corr, fp);
                }
                if (thisleapexpiry) {
                  /* Append a no-op leap correction indicating when the leap
@@ -3053,7 +3061,7 @@ writezone(const char *const name, const char *const 
string, char version,
                     the plan is to amend the RFC to allow this in version 4
                     TZif files.  */
                  puttzcodepass(leapexpires, fp, pass);
-                 puttzcode(thisleaplim ? corr[thisleaplim - 1] : 0, fp);
+                 puttzcode(thisleaplim ? leap[thisleaplim - 1].corr : 0, fp);
                }
                if (stdcnt != 0)
                  for (i = old0; i < typecnt; i++)
@@ -3813,32 +3821,27 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool 
ttisstd, bool ttisut)
 static void
 leapadd(zic_t t, int correction, int rolling)
 {
-       register int i;
+       register ptrdiff_t i;
 
-       if (TZ_MAX_LEAPS <= leapcnt) {
-               error(_("too many leap seconds"));
-               exit(EXIT_FAILURE);
-       }
        if (rolling && (lo_time != min_time || hi_time != max_time)) {
          error(_("Rolling leap seconds not supported with -r"));
          exit(EXIT_FAILURE);
        }
+       leap = growalloc(leap, sizeof *leap, leapcnt, &leap_alloc);
        for (i = 0; i < leapcnt; ++i)
-               if (t <= trans[i])
+               if (t <= leap[i].trans)
                        break;
-       memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans);
-       memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr);
-       memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll);
-       trans[i] = t;
-       corr[i] = correction;
-       roll[i] = rolling;
+       memmove(&leap[i + 1], &leap[i], (leapcnt - i) * sizeof *leap);
+       leap[i].trans = t;
+       leap[i].corr = correction;
+       leap[i].roll = rolling;
        ++leapcnt;
 }
 
 static void
 adjleap(void)
 {
-       register int    i;
+       register ptrdiff_t i;
        register zic_t  last = 0;
        register zic_t  prevtrans = 0;
 
@@ -3846,18 +3849,18 @@ adjleap(void)
        ** propagate leap seconds forward
        */
        for (i = 0; i < leapcnt; ++i) {
-               if (trans[i] - prevtrans < 28 * SECSPERDAY) {
+               if (leap[i].trans - prevtrans < 28 * SECSPERDAY) {
                  error(_("Leap seconds too close together"));
                  exit(EXIT_FAILURE);
                }
-               prevtrans = trans[i];
-               trans[i] = tadd(trans[i], last);
-               last = corr[i] += last;
+               prevtrans = leap[i].trans;
+               leap[i].trans = tadd(prevtrans, last);
+               last = leap[i].corr += last;
        }
 
        if (0 <= leapexpires) {
          leapexpires = oadd(leapexpires, last);
-         if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) {
+         if (! (leapcnt == 0 || (leap[leapcnt - 1].trans < leapexpires))) {
            error(_("last Leap time does not precede Expires time"));
            exit(EXIT_FAILURE);
          }
-- 
2.51.0

Reply via email to