<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>