[tz] [PATCH 8/8] Improve zdump -v tm_year extrema display

Tim Parenti tim at timtimeonline.com
Tue Sep 14 02:32:26 UTC 2021


The prior behavior was offset from absolute_{min,max}_time by SECSPERDAY;
the attached fixes this description of the prior behavior in NEWS.

--
Tim Parenti


On Wed, 24 Mar 2021 at 00:01, Paul Eggert via tz <tz at iana.org> wrote:

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


More information about the tz mailing list