<div dir="ltr"><div>The prior behavior was offset from absolute_{min,max}_time by SECSPERDAY; the attached fixes this description of the prior behavior in NEWS.</div><br clear="all"><div><div dir="ltr" data-smartmail="gmail_signature">--<br>Tim Parenti<br></div></div><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, 24 Mar 2021 at 00:01, Paul Eggert via tz <<a href="mailto:tz@iana.org" target="_blank">tz@iana.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">* NEWS, zdump.8: Document the new behavior.<br>
* zdump.c (main): Let showextrema do the work.<br>
Don’t bother with the one-hour-away timestamp.<br>
(hunt): New args LOTMP and ONLY_OK. All callers changed.<br>
(showextrema): New function.  Don’t assume there’s just a single<br>
transition near the boundary; there could be more if a timezone or<br>
leap-second transition is nearby.<br>
---<br>
 NEWS    |   5 +++<br>
 zdump.8 |  17 +++++----<br>
 zdump.c | 112 ++++++++++++++++++++++++++++++++++++++++----------------<br>
 3 files changed, 96 insertions(+), 38 deletions(-)<br>
<br>
diff --git a/NEWS b/NEWS<br>
index 20b789c..593c3fa 100644<br>
--- a/NEWS<br>
+++ b/NEWS<br>
@@ -90,6 +90,11 @@ Unreleased, experimental changes<br>
     seconds on the rare platforms where time_t counts leap seconds,<br>
     fixing a bug introduced in 2014g.<br>
<br>
+    zdump -v now outputs timestamps at boundaries of what localtime<br>
+    and gmtime can represent, instead of the less-useful timestamps<br>
+    one hour after the minimum and one hour before the maximum.<br>
+    (Thanks to Arthur David Olson for prototype code.)<br>
+<br>
     zdump's -c and -t options are now consistently inclusive for the<br>
     lower time bound and exclusive for the upper.  Formerly they were<br>
     inconsistent.  (Confusion noted by Martin Burnicki.)<br>
diff --git a/zdump.8 b/zdump.8<br>
index 17c0200..a5e2042 100644<br>
--- a/zdump.8<br>
+++ b/zdump.8<br>
@@ -44,12 +44,14 @@ Output a verbose description of time intervals.<br>
 For each<br>
 .I timezone<br>
 on the command line,<br>
-print the time at the lowest possible time value,<br>
-the time one day after the lowest possible time value,<br>
+print the times at the two extreme time values,<br>
+the times (if present) at and just beyond the boundaries of years that<br>
+.BR localtime (3)<br>
+and<br>
+.BR gmtime (3)<br>
+can represent, and<br>
 the times both one second before and exactly at<br>
-each detected time discontinuity,<br>
-the time at one day less than the highest possible time value,<br>
-and the time at the highest possible time value.<br>
+each detected time discontinuity.<br>
 Each line is followed by<br>
 .BI isdst= D<br>
 where<br>
@@ -66,7 +68,7 @@ seconds east of Greenwich.<br>
 .B \*-V<br>
 Like<br>
 .BR \*-v ,<br>
-except omit the times relative to the extreme time values.<br>
+except omit output concerning extreme time and year values.<br>
 This generates output that is easier to compare to that of<br>
 implementations with different time representations.<br>
 .TP<br>
@@ -200,7 +202,8 @@ This time zone is east of UT, so its UT offsets are positive.  Also,<br>
 many of its time zone abbreviations are omitted since they duplicate<br>
 the text of the UT offset.<br>
 .SH LIMITATIONS<br>
-Time discontinuities are found by sampling the results returned by localtime<br>
+Time discontinuities are found by sampling the results returned by<br>
+.BR localtime (3)<br>
 at twelve-hour intervals.<br>
 This works in all real-world cases;<br>
 one can construct artificial time zones for which this fails.<br>
diff --git a/zdump.c b/zdump.c<br>
index fbe53cd..e1db329 100644<br>
--- a/zdump.c<br>
+++ b/zdump.c<br>
@@ -91,8 +91,9 @@ static bool   errout;<br>
 static char const *abbr(struct tm const *);<br>
 static intmax_t        delta(struct tm *, struct tm *) ATTRIBUTE_PURE;<br>
 static void dumptime(struct tm const *);<br>
-static time_t hunt(timezone_t, char *, time_t, time_t);<br>
+static time_t hunt(timezone_t, char *, time_t, struct tm *, time_t, bool);<br>
 static void show(timezone_t, char *, time_t, bool);<br>
+static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);<br>
 static void showtrans(char const *, struct tm const *, time_t, char const *,<br>
                      char const *);<br>
 static const char *tformat(void);<br>
@@ -533,6 +534,7 @@ main(int argc, char *argv[])<br>
                char const *ab;<br>
                time_t t;<br>
                struct tm tm, newtm;<br>
+               struct tm *tmp;<br>
                bool tm_ok;<br>
                if (!tz) {<br>
                  perror(argv[i]);<br>
@@ -547,21 +549,19 @@ main(int argc, char *argv[])<br>
                t = absolute_min_time;<br>
                if (! (iflag | Vflag)) {<br>
                        show(tz, argv[i], t, true);<br>
-                       t += SECSPERDAY;<br>
-                       show(tz, argv[i], t, true);<br>
-                       if (my_localtime_rz(tz, &t, &tm) == NULL && t < cutlotime) {<br>
+                       if (my_localtime_rz(tz, &t, &tm) == NULL<br>
+                           && t < cutlotime) {<br>
                                time_t newt = cutlotime;<br>
-                               if (my_localtime_rz(tz, &newt, &newtm) != NULL) {<br>
-                                       newt = hunt(tz, argv[i], t, newt);<br>
-                                       show(tz, argv[i], newt - 1, true);<br>
-                                       show(tz, argv[i], newt, true);<br>
-                               }<br>
+                               if (my_localtime_rz(tz, &newt, &newtm)<br>
+                                   != NULL)<br>
+                                 showextrema(tz, argv[i], t, NULL, newt);<br>
                        }<br>
                }<br>
                if (t + 1 < cutlotime)<br>
                  t = cutlotime - 1;<br>
                INITIALIZE (ab);<br>
-               tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;<br>
+               tmp = my_localtime_rz(tz, &t, &tm);<br>
+               tm_ok = tmp != NULL;<br>
                if (tm_ok) {<br>
                  ab = saveabbr(&abbrev, &abbrevsize, &tm);<br>
                  if (iflag) {<br>
@@ -580,7 +580,7 @@ main(int argc, char *argv[])<br>
                      || (tm_ok && (delta(&newtm, &tm) != newt - t<br>
                                    || newtm.tm_isdst != tm.tm_isdst<br>
                                    || strcmp(abbr(&newtm), ab) != 0))) {<br>
-                   newt = hunt(tz, argv[i], t, newt);<br>
+                   newt = hunt(tz, argv[i], t, tmp, newt, false);<br>
                    newtmp = localtime_rz(tz, &newt, &newtm);<br>
                    newtm_ok = newtmp != NULL;<br>
                    if (iflag)<br>
@@ -600,20 +600,14 @@ main(int argc, char *argv[])<br>
                }<br>
                if (! (iflag | Vflag)) {<br>
                        time_t newt = absolute_max_time;<br>
-                       newt -= SECSPERDAY;<br>
                        t = cuthitime;<br>
-                       if (t < newt &&<br>
-                               my_localtime_rz(tz, &t, &tm) != NULL &&<br>
-                               my_localtime_rz(tz, &newt, &newtm) == NULL) {<br>
-                               newt = hunt(tz, argv[i], t, newt);<br>
-                               show(tz, argv[i], newt - 1, true);<br>
-                               show(tz, argv[i], newt, true);<br>
+                       if (t < newt) {<br>
+                         tmp = my_localtime_rz(tz, &t, &tm);<br>
+                         if (tmp != NULL<br>
+                             && my_localtime_rz(tz, &newt, &newtm) == NULL)<br>
+                           showextrema(tz, argv[i], t, tmp, newt);<br>
                        }<br>
-                       t = absolute_max_time;<br>
-                       t -= SECSPERDAY;<br>
-                       show(tz, argv[i], t, true);<br>
-                       t += SECSPERDAY;<br>
-                       show(tz, argv[i], t, true);<br>
+                       show(tz, argv[i], absolute_max_time, true);<br>
                }<br>
                tzfree(tz);<br>
        }<br>
@@ -666,19 +660,30 @@ yeartot(intmax_t y)<br>
        return t;<br>
 }<br>
<br>
+/* Search for a discontinuity in timezone TZ with name NAME, in the<br>
+   timestamps ranging from LOT (with broken-down time LOTMP if<br>
+   nonnull) through HIT.  LOT and HIT disagree about some aspect of<br>
+   timezone.  If ONLY_OK, search only for definedness changes, i.e.,<br>
+   localtime succeeds on one side of the transition but fails on the<br>
+   other side.  Return the timestamp just before the transition from<br>
+   LOT's settings.  */<br>
+<br>
 static time_t<br>
-hunt(timezone_t tz, char *name, time_t lot, time_t hit)<br>
+hunt(timezone_t tz, char *name, time_t lot, struct tm *lotmp, time_t hit,<br>
+     bool only_ok)<br>
 {<br>
        static char *           loab;<br>
        static size_t           loabsize;<br>
        char const *            ab;<br>
        struct tm               lotm;<br>
        struct tm               tm;<br>
-       bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;<br>
+       bool lotm_ok = lotmp != NULL;<br>
        bool tm_ok;<br>
<br>
-       if (lotm_ok)<br>
+       if (lotm_ok) {<br>
+         lotm = *lotmp;<br>
          ab = saveabbr(&loab, &loabsize, &lotm);<br>
+       }<br>
        for ( ; ; ) {<br>
                /* T = average of LOT and HIT, rounding down.<br>
                   Avoid overflow, even on oddball C89 platforms<br>
@@ -691,11 +696,11 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit)<br>
                if (t == lot)<br>
                        break;<br>
                tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;<br>
-               if (lotm_ok & tm_ok<br>
-                   ? (delta(&tm, &lotm) == t - lot<br>
-                      && tm.tm_isdst == lotm.tm_isdst<br>
-                      && strcmp(abbr(&tm), ab) == 0)<br>
-                   : lotm_ok == tm_ok) {<br>
+               if (lotm_ok == tm_ok<br>
+                   && (only_ok<br>
+                       || (lotm_ok && tm.tm_isdst == lotm.tm_isdst<br>
+                           && delta(&tm, &lotm) == t - lot<br>
+                           && strcmp(abbr(&tm), ab) == 0))) {<br>
                  lot = t;<br>
                  if (tm_ok)<br>
                    lotm = tm;<br>
@@ -819,6 +824,51 @@ show(timezone_t tz, char *zone, time_t t, bool v)<br>
                abbrok(abbr(tmp), zone);<br>
 }<br>
<br>
+/* Show timestamps just before and just after a transition between<br>
+   defined and undefined (or vice versa) in either localtime or<br>
+   gmtime.  These transitions are for timezone TZ with name ZONE, in<br>
+   the range from LO (with broken-down time LOTMP if that is nonnull)<br>
+   through HI.  LO and HI disagree on definedness.  */<br>
+<br>
+static void<br>
+showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)<br>
+{<br>
+  struct tm localtm[2], gmtm[2];<br>
+  time_t t, boundary = hunt(tz, zone, lo, lotmp, hi, true);<br>
+  bool old = false;<br>
+  hi = (SECSPERDAY < hi - boundary<br>
+       ? boundary + SECSPERDAY<br>
+       : hi + (hi < TIME_T_MAX));<br>
+  if (SECSPERDAY < boundary - lo) {<br>
+    lo = boundary - SECSPERDAY;<br>
+    lotmp = my_localtime_rz(tz, &lo, &localtm[old]);<br>
+  }<br>
+  if (lotmp)<br>
+    localtm[old] = *lotmp;<br>
+  else<br>
+    localtm[old].tm_sec = -1;<br>
+  if (! my_gmtime_r(&lo, &gmtm[old]))<br>
+    gmtm[old].tm_sec = -1;<br>
+<br>
+  /* Search sequentially for definedness transitions.  Although this<br>
+     could be sped up by refining 'hunt' to search for either<br>
+     localtime or gmtime definedness transitions, it hardly seems<br>
+     worth the trouble.  */<br>
+  for (t = lo + 1; t < hi; t++) {<br>
+    bool new = !old;<br>
+    if (! my_localtime_rz(tz, &t, &localtm[new]))<br>
+      localtm[new].tm_sec = -1;<br>
+    if (! my_gmtime_r(&t, &gmtm[new]))<br>
+      gmtm[new].tm_sec = -1;<br>
+    if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))<br>
+       | ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {<br>
+      show(tz, zone, t - 1, true);<br>
+      show(tz, zone, t, true);<br>
+    }<br>
+    old = new;<br>
+  }<br>
+}<br>
+<br>
 #if HAVE_SNPRINTF<br>
 # define my_snprintf snprintf<br>
 #else<br>
-- <br>
2.27.0<br>
<br>
</blockquote></div>