1 /*        $NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $  */
2 /* Dump time zone data in a textual format.  */
3 
4 /*
5 ** This file is in the public domain, so clarified as of
6 ** 2009-05-17 by Arthur David Olson.
7 */
8 
9 #include <sys/cdefs.h>
10 #ifndef lint
11 __RCSID("$NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $");
12 #endif /* !defined lint */
13 
14 #ifndef NETBSD_INSPIRED
15 # define NETBSD_INSPIRED 1
16 #endif
17 
18 #include <err.h>
19 #include "private.h"
20 #include <stdio.h>
21 
22 #ifndef HAVE_LOCALTIME_R
23 # define HAVE_LOCALTIME_R 1
24 #endif
25 
26 #ifndef HAVE_LOCALTIME_RZ
27 # ifdef TM_ZONE
28 #  define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
29 # else
30 #  define HAVE_LOCALTIME_RZ 0
31 # endif
32 #endif
33 
34 #ifndef HAVE_TZSET
35 # define HAVE_TZSET 1
36 #endif
37 
38 #ifndef ZDUMP_LO_YEAR
39 # define ZDUMP_LO_YEAR (-500)
40 #endif /* !defined ZDUMP_LO_YEAR */
41 
42 #ifndef ZDUMP_HI_YEAR
43 # define ZDUMP_HI_YEAR 2500
44 #endif /* !defined ZDUMP_HI_YEAR */
45 
46 #define SECSPERNYEAR          (SECSPERDAY * DAYSPERNYEAR)
47 #define SECSPERLYEAR          (SECSPERNYEAR + SECSPERDAY)
48 #define SECSPER400YEARS       (SECSPERNYEAR * (intmax_t) (300 + 3)    \
49                                + SECSPERLYEAR * (intmax_t) (100 - 3))
50 
51 /*
52 ** True if SECSPER400YEARS is known to be representable as an
53 ** intmax_t.  It's OK that SECSPER400YEARS_FITS can in theory be false
54 ** even if SECSPER400YEARS is representable, because when that happens
55 ** the code merely runs a bit more slowly, and this slowness doesn't
56 ** occur on any practical platform.
57 */
58 enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
59 
60 #if HAVE_GETTEXT
61 # include <locale.h> /* for setlocale */
62 #endif /* HAVE_GETTEXT */
63 
64 #if ! HAVE_LOCALTIME_RZ
65 # undef  timezone_t
66 # define timezone_t char **
67 #endif
68 
69 #if !HAVE_POSIX_DECLS
70 extern int          getopt(int argc, char * const argv[],
71                               const char * options);
72 extern char *       optarg;
73 extern int          optind;
74 #endif
75 
76 /* The minimum and maximum finite time values.  */
77 enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
78 static time_t       absolute_min_time =
79   ((time_t) -1 < 0
80     ? (- ((time_t) ~ (time_t) 0 < 0)
81        - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
82     : 0);
83 static time_t       absolute_max_time =
84   ((time_t) -1 < 0
85     ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
86    : -1);
87 static size_t       longest;
88 static char const *progname;
89 static bool         warned;
90 static bool         errout;
91 
92 static char const *abbr(struct tm const *);
93 static intmax_t delta(struct tm *, struct tm *);
94 static void dumptime(struct tm const *);
95 static time_t hunt(timezone_t, time_t, time_t, bool);
96 static void show(timezone_t, char *, time_t, bool);
97 static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
98 static void showtrans(char const *, struct tm const *, time_t, char const *,
99                           char const *);
100 static const char *tformat(void);
101 ATTRIBUTE_PURE_114833 static time_t yeartot(intmax_t);
102 
103 /* Is C an ASCII digit?  */
104 static bool
is_digit(char c)105 is_digit(char c)
106 {
107   return '0' <= c && c <= '9';
108 }
109 
110 /* Is A an alphabetic character in the C locale?  */
111 static bool
is_alpha(char a)112 is_alpha(char a)
113 {
114           switch (a) {
115             default:
116                     return false;
117             case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
118             case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
119             case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
120             case 'V': case 'W': case 'X': case 'Y': case 'Z':
121             case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
122             case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
123             case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
124             case 'v': case 'w': case 'x': case 'y': case 'z':
125                     return true;
126           }
127 }
128 
129 ATTRIBUTE_NORETURN static void
size_overflow(void)130 size_overflow(void)
131 {
132   fprintf(stderr, _("%s: size overflow\n"), progname);
133   exit(EXIT_FAILURE);
134 }
135 
136 /* Return A + B, exiting if the result would overflow either ptrdiff_t
137    or size_t.  A and B are both nonnegative.  */
138 ATTRIBUTE_PURE_114833 static ptrdiff_t
sumsize(size_t a,size_t b)139 sumsize(size_t a, size_t b)
140 {
141 #ifdef ckd_add
142   ptrdiff_t sum;
143   if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
144     return sum;
145 #else
146   if (a <= INDEX_MAX && b <= INDEX_MAX - a)
147     return a + b;
148 #endif
149   size_overflow();
150 }
151 
152 
153 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
154    on failure.  SIZE should be positive.  */
155 static void *
xmalloc(ptrdiff_t size)156 xmalloc(ptrdiff_t size)
157 {
158   void *p = malloc(size);
159   if (!p) {
160     fprintf(stderr, _("%s: Memory exhausted\n"), progname);
161     exit(EXIT_FAILURE);
162   }
163   return p;
164 }
165 
166 #if ! HAVE_TZSET
167 # undef tzset
168 # define tzset zdump_tzset
tzset(void)169 static void tzset(void) { }
170 #endif
171 
172 /* Assume gmtime_r works if localtime_r does.
173    A replacement localtime_r is defined below if needed.  */
174 #if ! HAVE_LOCALTIME_R
175 
176 # undef gmtime_r
177 # define gmtime_r zdump_gmtime_r
178 
179 static struct tm *
gmtime_r(time_t * tp,struct tm * tmp)180 gmtime_r(time_t *tp, struct tm *tmp)
181 {
182           struct tm *r = gmtime(tp);
183           if (r) {
184                     *tmp = *r;
185                     r = tmp;
186           }
187           return r;
188 }
189 
190 #endif
191 
192 /* Platforms with TM_ZONE don't need tzname, so they can use the
193    faster localtime_rz or localtime_r if available.  */
194 
195 #if defined TM_ZONE && HAVE_LOCALTIME_RZ
196 # define USE_LOCALTIME_RZ true
197 #else
198 # define USE_LOCALTIME_RZ false
199 #endif
200 
201 #if ! USE_LOCALTIME_RZ
202 
203 # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
204 #  undef localtime_r
205 #  define localtime_r zdump_localtime_r
206 static struct tm *
localtime_r(time_t * tp,struct tm * tmp)207 localtime_r(time_t *tp, struct tm *tmp)
208 {
209           struct tm *r = localtime(tp);
210           if (r) {
211                     *tmp = *r;
212                     r = tmp;
213           }
214           return r;
215 }
216 # endif
217 
218 # undef localtime_rz
219 # define localtime_rz zdump_localtime_rz
220 static struct tm *
localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz,time_t * tp,struct tm * tmp)221 localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz, time_t *tp, struct tm *tmp)
222 {
223           return localtime_r(tp, tmp);
224 }
225 
226 # ifdef TYPECHECK
227 #  undef mktime_z
228 #  define mktime_z zdump_mktime_z
229 static time_t
mktime_z(timezone_t tz,struct tm * tmp)230 mktime_z(timezone_t tz, struct tm *tmp)
231 {
232           return mktime(tmp);
233 }
234 # endif
235 
236 # undef tzalloc
237 # undef tzfree
238 # define tzalloc zdump_tzalloc
239 # define tzfree zdump_tzfree
240 
241 static timezone_t
tzalloc(char const * val)242 tzalloc(char const *val)
243 {
244 # if HAVE_SETENV
245   if (setenv("TZ", val, 1) != 0) {
246     char const *e = strerror(errno);
247     fprintf(stderr, _("%s: setenv: %s\n"), progname, e);
248     exit(EXIT_FAILURE);
249   }
250   tzset();
251   return &optarg;  /* Any valid non-null char ** will do.  */
252 # else
253   enum { TZeqlen = 3 };
254   static char const TZeq[TZeqlen] = "TZ=";
255   static ptrdiff_t fakeenv0size;
256   void *freeable = NULL;
257   char **env = fakeenv, **initial_environ;
258   ptrdiff_t valsize = strlen(val) + 1;
259   if (fakeenv0size < valsize) {
260     char **e = environ, **to;
261     ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
262 
263     while (*e++) {
264 #  ifdef ckd_add
265       if (ckd_add(&initial_nenvptrs, initial_nenvptrs, 1)
266             || INDEX_MAX < initial_nenvptrs)
267           size_overflow();
268 #  else
269       if (initial_nenvptrs == INDEX_MAX / sizeof *environ)
270           size_overflow();
271       initial_nenvptrs++;
272 #  endif
273     fakeenv0size = sumsize(valsize, valsize);
274     fakeenv0size = max(fakeenv0size, 64);
275     freeable = env;
276     fakeenv = env =
277       xmalloc(sumsize(sumsize(sizeof *environ,
278                                     initial_nenvptrs * sizeof *environ),
279                           sumsize(TZeqlen, fakeenv0size)));
280     to = env + 1;
281     for (e = environ; (*to = *e); e++)
282       to += strncmp(*e, TZeq, TZeqlen) != 0;
283     env[0] = memcpy(to + 1, TZeq, TZeqlen);
284   }
285   memcpy(env[0] + TZeqlen, val, valsize);
286   initial_environ = environ;
287   environ = env;
288   tzset();
289   free(freeable);
290   return initial_environ;
291 # endif
292 }
293 
294 static void
295 tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ)
296 {
297 # if !HAVE_SETENV
298   environ = initial_environ;
299   tzset();
300 # endif
301 }
302 #endif /* ! USE_LOCALTIME_RZ */
303 
304 /* A UT time zone, and its initializer.  */
305 static timezone_t gmtz;
306 static void
307 gmtzinit(void)
308 {
309   if (USE_LOCALTIME_RZ) {
310     /* Try "GMT" first to find out whether this is one of the rare
311        platforms where time_t counts leap seconds; this works due to
312        the "Zone GMT 0 - GMT" line in the "etcetera" file.  If "GMT"
313        fails, fall back on "GMT0" which might be similar due to the
314        "Link GMT GMT0" line in the "backward" file, and which
315        should work on all POSIX platforms.  The rest of zdump does not
316        use the "GMT" abbreviation that comes from this setting, so it
317        is OK to use "GMT" here rather than the modern "UTC" which
318        would not work on platforms that omit the "backward" file.  */
319     gmtz = tzalloc("GMT");
320     if (!gmtz) {
321       static char const gmt0[] = "GMT0";
322       gmtz = tzalloc(gmt0);
323       if (!gmtz) {
324           err(EXIT_FAILURE, "Cannot create %s", gmt0);
325       }
326     }
327   }
328 }
329 
330 /* Convert *TP to UT, storing the broken-down time into *TMP.
331    Return TMP if successful, NULL otherwise.  This is like gmtime_r(TP, TMP),
332    except typically faster if USE_LOCALTIME_RZ.  */
333 static struct tm *
334 my_gmtime_r(time_t *tp, struct tm *tmp)
335 {
336           return USE_LOCALTIME_RZ ?
337               localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
338 }
339 
340 #ifndef TYPECHECK
341 #define my_localtime_rz       localtime_rz
342 #else /* !defined TYPECHECK */
343 static struct tm *
344 my_localtime_rz(timezone_t tz, const time_t *tp, struct tm *tmp)
345 {
346           tmp = localtime_rz(tz, tp, tmp);
347           if (tmp) {
348                     struct tm tm;
349                     time_t    t;
350 
351                     tm = *tmp;
352                     t = mktime_z(tz, &tm);
353                     if (t != *tp) {
354                               (void) fflush(stdout);
355                               (void) fprintf(stderr, "\n%s: ", progname);
356                               (void) fprintf(stderr, tformat(), *tp);
357                               (void) fprintf(stderr, " ->");
358                               (void) fprintf(stderr, " year=%d", tmp->tm_year);
359                               (void) fprintf(stderr, " mon=%d", tmp->tm_mon);
360                               (void) fprintf(stderr, " mday=%d", tmp->tm_mday);
361                               (void) fprintf(stderr, " hour=%d", tmp->tm_hour);
362                               (void) fprintf(stderr, " min=%d", tmp->tm_min);
363                               (void) fprintf(stderr, " sec=%d", tmp->tm_sec);
364                               (void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
365                               (void) fprintf(stderr, " -> ");
366                               (void) fprintf(stderr, tformat(), t);
367                               (void) fprintf(stderr, "\n");
368                               errout = true;
369                     }
370           }
371           return tmp;
372 }
373 #endif /* !defined TYPECHECK */
374 
375 static void
376 abbrok(const char *const abbrp, const char *const zone)
377 {
378           const char *cp;
379           const char *wp;
380 
381           if (warned)
382                     return;
383           cp = abbrp;
384           while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
385                     ++cp;
386           if (*cp)
387                     wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
388           else if (cp - abbrp < 3)
389                     wp = _("has fewer than 3 characters");
390           else if (cp - abbrp > 6)
391                     wp = _("has more than 6 characters");
392           else
393                     return;
394           (void) fflush(stdout);
395           (void) fprintf(stderr,
396                     _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
397                     progname, zone, abbrp, wp);
398           warned = errout = true;
399 }
400 
401 /* Return a time zone abbreviation.  If the abbreviation needs to be
402    saved, use *BUF (of size *BUFALLOC) to save it, and return the
403    abbreviation in the possibly reallocated *BUF.  Otherwise, just
404    return the abbreviation.  Get the abbreviation from TMP.
405    Exit on memory allocation failure.  */
406 static char const *
407 saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
408 {
409           char const *ab = abbr(tmp);
410           if (HAVE_LOCALTIME_RZ)
411                     return ab;
412           else {
413                     ptrdiff_t absize = strlen(ab) + 1;
414                     if (*bufalloc < absize) {
415                               free(*buf);
416 
417                               /* Make the new buffer at least twice as long as the
418                                  old, to avoid O(N**2) behavior on repeated calls.  */
419                               *bufalloc = sumsize(*bufalloc, absize);
420                               *buf = xmalloc(*bufalloc);
421                     }
422                     return strcpy(*buf, ab);
423           }
424 }
425 
426 static void
427 close_file(FILE *stream)
428 {
429           char const *e = (ferror(stream) ? _("I/O error")
430               : fclose(stream) != 0 ? strerror(errno) : NULL);
431           if (e) {
432                     errx(EXIT_FAILURE, "%s", e);
433           }
434 }
435 
436 __dead static void
437 usage(FILE *const stream, const int status)
438 {
439           (void) fprintf(stream,
440 _("%s: usage: %s OPTIONS TIMEZONE ...\n"
441   "Options include:\n"
442   "  -c [L,]U   Start at year L (default -500), end before year U (default 2500)\n"
443   "  -t [L,]U   Start at time L, end before time U (in seconds since 1970)\n"
444   "  -i         List transitions briefly (format is experimental)\n" \
445   "  -v         List transitions verbosely\n"
446   "  -V         List transitions a bit less verbosely\n"
447   "  --help     Output this help\n"
448   "  --version  Output version info\n"
449   "\n"
450   "Report bugs to %s.\n"),
451              progname, progname, REPORT_BUGS_TO);
452           if (status == EXIT_SUCCESS)
453                     close_file(stream);
454           exit(status);
455 }
456 
457 int
458 main(int argc, char *argv[])
459 {
460           /* These are static so that they're initially zero.  */
461           static char *                 abbrev;
462           static ptrdiff_t    abbrevsize;
463 
464           int                 i;
465           bool                vflag;
466           bool                Vflag;
467           char *              cutarg;
468           char *              cuttimes;
469           time_t              cutlotime;
470           time_t              cuthitime;
471           time_t              now;
472           bool iflag = false;
473           size_t arglenmax = 0;
474 
475           cutlotime = absolute_min_time;
476           cuthitime = absolute_max_time;
477 #if HAVE_GETTEXT
478           (void) setlocale(LC_ALL, "");
479 # ifdef TZ_DOMAINDIR
480           (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
481 # endif /* defined TEXTDOMAINDIR */
482           (void) textdomain(TZ_DOMAIN);
483 #endif /* HAVE_GETTEXT */
484           progname = argv[0] ? argv[0] : "zdump";
485           for (i = 1; i < argc; ++i)
486                     if (strcmp(argv[i], "--version") == 0) {
487                               (void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
488                               return EXIT_SUCCESS;
489                     } else if (strcmp(argv[i], "--help") == 0) {
490                               usage(stdout, EXIT_SUCCESS);
491                     }
492           vflag = Vflag = false;
493           cutarg = cuttimes = NULL;
494           for (;;)
495             switch (getopt(argc, argv, "c:it:vV")) {
496             case 'c': cutarg = optarg; break;
497             case 't': cuttimes = optarg; break;
498             case 'i': iflag = true; break;
499             case 'v': vflag = true; break;
500             case 'V': Vflag = true; break;
501             case -1:
502               if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
503                 goto arg_processing_done;
504               ATTRIBUTE_FALLTHROUGH;
505             default:
506               usage(stderr, EXIT_FAILURE);
507             }
508  arg_processing_done:;
509 
510           if (iflag | vflag | Vflag) {
511                     intmax_t  lo;
512                     intmax_t  hi;
513                     char *loend, *hiend;
514                     intmax_t cutloyear = ZDUMP_LO_YEAR;
515                     intmax_t cuthiyear = ZDUMP_HI_YEAR;
516                     if (cutarg != NULL) {
517                               lo = strtoimax(cutarg, &loend, 10);
518                               if (cutarg != loend && !*loend) {
519                                         hi = lo;
520                                         cuthiyear = hi;
521                               } else if (cutarg != loend && *loend == ','
522                                            && (hi = strtoimax(loend + 1, &hiend, 10),
523                                                loend + 1 != hiend && !*hiend)) {
524                                         cutloyear = lo;
525                                         cuthiyear = hi;
526                               } else {
527                                         fprintf(stderr, _("%s: wild -c argument %s\n"),
528                                                   progname, cutarg);
529                                         return EXIT_FAILURE;
530                               }
531                     }
532                     if (cutarg != NULL || cuttimes == NULL) {
533                               cutlotime = yeartot(cutloyear);
534                               cuthitime = yeartot(cuthiyear);
535                     }
536                     if (cuttimes != NULL) {
537                               lo = strtoimax(cuttimes, &loend, 10);
538                               if (cuttimes != loend && !*loend) {
539                                         hi = lo;
540                                         if (hi < cuthitime) {
541                                                   if (hi < absolute_min_time + 1)
542                                                     hi = absolute_min_time + 1;
543                                                   cuthitime = hi;
544                                         }
545                               } else if (cuttimes != loend && *loend == ','
546                                            && (hi = strtoimax(loend + 1, &hiend, 10),
547                                                loend + 1 != hiend && !*hiend)) {
548                                         if (cutlotime < lo) {
549                                                   if (absolute_max_time < lo)
550                                                             lo = absolute_max_time;
551                                                   cutlotime = lo;
552                                         }
553                                         if (hi < cuthitime) {
554                                                   if (hi < absolute_min_time + 1)
555                                                     hi = absolute_min_time + 1;
556                                                   cuthitime = hi;
557                                         }
558                               } else {
559                                         (void) fprintf(stderr,
560                                                   _("%s: wild -t argument %s\n"),
561                                                   progname, cuttimes);
562                                         return EXIT_FAILURE;
563                               }
564                     }
565           }
566           gmtzinit();
567           if (iflag | vflag | Vflag)
568             now = 0;
569           else {
570             now = time(NULL);
571             now |= !now;
572           }
573           for (i = optind; i < argc; i++) {
574             size_t arglen = strlen(argv[i]);
575             if (arglenmax < arglen)
576               arglenmax = arglen;
577           }
578           if (!HAVE_SETENV && INDEX_MAX <= arglenmax)
579             size_overflow();
580           longest = min(arglenmax, INT_MAX - 2);
581 
582           for (i = optind; i < argc; ++i) {
583                     /* Treat "-" as standard input on platforms with /dev/stdin.
584                        It's not worth the bother of supporting "-" on other
585                        platforms, as that would need temp files.  */
586                     timezone_t tz = tzalloc(strcmp(argv[i], "-") == 0
587                                                   ? "/dev/stdin" : argv[i]);
588                     char const *ab;
589                     time_t t;
590                     struct tm tm, newtm;
591                     bool tm_ok;
592 
593                     if (!tz) {
594                               err(EXIT_FAILURE, "%s", argv[i]);
595                     }
596                     if (now) {
597                               show(tz, argv[i], now, false);
598                               tzfree(tz);
599                               continue;
600                     }
601                     warned = false;
602                     t = absolute_min_time;
603                     if (! (iflag | Vflag)) {
604                               show(tz, argv[i], t, true);
605                               if (my_localtime_rz(tz, &t, &tm) == NULL
606                                   && t < cutlotime) {
607                                         time_t newt = cutlotime;
608                                         if (my_localtime_rz(tz, &newt, &newtm) != NULL)
609                                           showextrema(tz, argv[i], t, NULL, newt);
610                               }
611                     }
612                     if (t + 1 < cutlotime)
613                       t = cutlotime - 1;
614                     tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
615                     if (tm_ok) {
616                               ab = saveabbr(&abbrev, &abbrevsize, &tm);
617                               if (iflag) {
618                                         showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
619                                         showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
620                               }
621                     } else
622                               ab = NULL;
623                     while (t < cuthitime - 1) {
624                               time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
625                                   && t + SECSPERDAY / 2 < cuthitime - 1)
626                                   ? t + SECSPERDAY / 2
627                                   : cuthitime - 1);
628                               struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
629                               bool newtm_ok = newtmp != NULL;
630                               if (tm_ok != newtm_ok
631                                   || (ab && (delta(&newtm, &tm) != newt - t
632                                                || newtm.tm_isdst != tm.tm_isdst
633                                                || strcmp(abbr(&newtm), ab) != 0))) {
634                                         newt = hunt(tz, t, newt, false);
635                                         newtmp = localtime_rz(tz, &newt, &newtm);
636                                         newtm_ok = newtmp != NULL;
637                                         if (iflag)
638                                                   showtrans("%Y-%m-%d\t%L\t%Q",
639                                                       newtmp, newt, newtm_ok ?
640                                                       abbr(&newtm) : NULL, argv[i]);
641                                         else {
642                                                   show(tz, argv[i], newt - 1, true);
643                                                   show(tz, argv[i], newt, true);
644                                         }
645                               }
646                               t = newt;
647                               tm_ok = newtm_ok;
648                               if (newtm_ok) {
649                                         ab = saveabbr(&abbrev, &abbrevsize, &newtm);
650                                         tm = newtm;
651                               }
652                     }
653                     if (! (iflag | Vflag)) {
654                               time_t newt = absolute_max_time;
655                               t = cuthitime;
656                               if (t < newt) {
657                                 struct tm *tmp = my_localtime_rz(tz, &t, &tm);
658                                 if (tmp != NULL
659                                     && my_localtime_rz(tz, &newt, &newtm) == NULL)
660                                   showextrema(tz, argv[i], t, tmp, newt);
661                               }
662                               show(tz, argv[i], absolute_max_time, true);
663                     }
664                     tzfree(tz);
665           }
666           close_file(stdout);
667           if (errout && (ferror(stderr) || fclose(stderr) != 0))
668                     return EXIT_FAILURE;
669           return EXIT_SUCCESS;
670 }
671 
672 static time_t
673 yeartot(intmax_t y)
674 {
675           intmax_t  myy, seconds, years;
676           time_t              t;
677 
678           myy = EPOCH_YEAR;
679           t = 0;
680           while (myy < y) {
681                     if (SECSPER400YEARS_FITS && 400 <= y - myy) {
682                               intmax_t diff400 = (y - myy) / 400;
683                               if (INTMAX_MAX / SECSPER400YEARS < diff400)
684                                         return absolute_max_time;
685                               seconds = diff400 * SECSPER400YEARS;
686                               years = diff400 * 400;
687                     } else {
688                               seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
689                               years = 1;
690                     }
691                     myy += years;
692                     if (t > absolute_max_time - seconds)
693                               return absolute_max_time;
694                     t += seconds;
695           }
696           while (y < myy) {
697                     if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
698                               intmax_t diff400 = (myy - y) / 400;
699                               if (INTMAX_MAX / SECSPER400YEARS < diff400)
700                                         return absolute_min_time;
701                               seconds = diff400 * SECSPER400YEARS;
702                               years = diff400 * 400;
703                     } else {
704                               seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
705                               years = 1;
706                     }
707                     myy -= years;
708                     if (t < absolute_min_time + seconds)
709                               return absolute_min_time;
710                     t -= seconds;
711           }
712           return t;
713 }
714 
715 /* Search for a discontinuity in timezone TZ, in the
716    timestamps ranging from LOT through HIT.  LOT and HIT disagree
717    about some aspect of timezone.  If ONLY_OK, search only for
718    definedness changes, i.e., localtime succeeds on one side of the
719    transition but fails on the other side.  Return the timestamp just
720    before the transition from LOT's settings.  */
721 
722 static time_t
723 hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
724 {
725           static char *                 loab;
726           static ptrdiff_t    loabsize;
727           struct tm           lotm;
728           struct tm           tm;
729 
730           /* Convert LOT into a broken-down time here, even though our
731              caller already did that.  On platforms without TM_ZONE,
732              tzname may have been altered since our caller broke down
733              LOT, and tzname needs to be changed back.  */
734           bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
735           bool tm_ok;
736           char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
737 
738           for ( ; ; ) {
739                     /* T = average of LOT and HIT, rounding down.
740                        Avoid overflow.  */
741                     int rem_sum = lot % 2 + hit % 2;
742                     time_t t = (rem_sum == 2) - (rem_sum < 0) + lot / 2 + hit / 2;
743                     if (t == lot)
744                               break;
745                     tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
746                     if (lotm_ok == tm_ok
747                         && (only_ok
748                               || (ab && tm.tm_isdst == lotm.tm_isdst
749                                   && delta(&tm, &lotm) == t - lot
750                                   && strcmp(abbr(&tm), ab) == 0))) {
751                       lot = t;
752                       if (tm_ok)
753                         lotm = tm;
754                     } else    hit = t;
755           }
756           return hit;
757 }
758 
759 /*
760 ** Thanks to Paul Eggert for logic used in delta_nonneg.
761 */
762 
763 static intmax_t
764 delta_nonneg(struct tm *newp, struct tm *oldp)
765 {
766           intmax_t oldy = oldp->tm_year;
767           int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
768           intmax_t sec = SECSPERREPEAT, result = cycles * sec;
769           int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
770           for ( ; tmy < newp->tm_year; ++tmy)
771                     result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
772           result += newp->tm_yday - oldp->tm_yday;
773           result *= HOURSPERDAY;
774           result += newp->tm_hour - oldp->tm_hour;
775           result *= MINSPERHOUR;
776           result += newp->tm_min - oldp->tm_min;
777           result *= SECSPERMIN;
778           result += newp->tm_sec - oldp->tm_sec;
779           return result;
780 }
781 
782 static intmax_t
783 delta(struct tm *newp, struct tm *oldp)
784 {
785   return (newp->tm_year < oldp->tm_year
786             ? -delta_nonneg(oldp, newp)
787             : delta_nonneg(newp, oldp));
788 }
789 
790 #ifndef TM_GMTOFF
791 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
792    Assume A and B differ by at most one year.  */
793 static int
794 adjusted_yday(struct tm const *a, struct tm const *b)
795 {
796           int yday = a->tm_yday;
797           if (b->tm_year < a->tm_year)
798                     yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
799           return yday;
800 }
801 #endif
802 
803 /* If A is the broken-down local time and B the broken-down UT for
804    the same instant, return A's UT offset in seconds, where positive
805    offsets are east of Greenwich.  On failure, return LONG_MIN.
806 
807    If T is nonnull, *T is the timestamp that corresponds to A; call
808    my_gmtime_r and use its result instead of B.  Otherwise, B is the
809    possibly nonnull result of an earlier call to my_gmtime_r.  */
810 static long
811 gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
812        ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
813 {
814 #ifdef TM_GMTOFF
815           return a->TM_GMTOFF;
816 #else
817           struct tm tm;
818           if (t)
819                     b = my_gmtime_r(t, &tm);
820           if (! b)
821                     return LONG_MIN;
822           else {
823                     int ayday = adjusted_yday(a, b);
824                     int byday = adjusted_yday(b, a);
825                     int days = ayday - byday;
826                     long hours = a->tm_hour - b->tm_hour + 24 * days;
827                     long minutes = a->tm_min - b->tm_min + 60 * hours;
828                     long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
829                     return seconds;
830           }
831 #endif
832 }
833 
834 static void
835 show(timezone_t tz, char *zone, time_t t, bool v)
836 {
837           struct tm *         tmp;
838           struct tm *         gmtmp;
839           struct tm tm, gmtm;
840 
841           (void) printf("%-*s  ", (int) longest, zone);
842           if (v) {
843                     gmtmp = my_gmtime_r(&t, &gmtm);
844                     if (gmtmp == NULL) {
845                               printf(tformat(), t);
846                               printf(_(" (gmtime failed)"));
847                     } else {
848                               dumptime(gmtmp);
849                               (void) printf(" UT");
850                     }
851                     (void) printf(" = ");
852           }
853           tmp = my_localtime_rz(tz, &t, &tm);
854           if (tmp == NULL) {
855                     printf(tformat(), t);
856                     printf(_(" (localtime failed)"));
857           } else {
858                     dumptime(tmp);
859                     if (*abbr(tmp) != '\0')
860                               (void) printf(" %s", abbr(tmp));
861                     if (v) {
862                               long off = gmtoff(tmp, NULL,  gmtmp);
863                               (void) printf(" isdst=%d", tmp->tm_isdst);
864                               if (off != LONG_MIN)
865                                         (void) printf(" gmtoff=%ld", off);
866                     }
867           }
868           (void) printf("\n");
869           if (tmp != NULL && *abbr(tmp) != '\0')
870                     abbrok(abbr(tmp), zone);
871 }
872 
873 /* Show timestamps just before and just after a transition between
874    defined and undefined (or vice versa) in either localtime or
875    gmtime.  These transitions are for timezone TZ with name ZONE, in
876    the range from LO (with broken-down time LOTMP if that is nonnull)
877    through HI.  LO and HI disagree on definedness.  */
878 
879 static void
880 showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
881 {
882   struct tm localtm[2], gmtm[2];
883   time_t t, boundary = hunt(tz, lo, hi, true);
884   bool old = false;
885   hi = (SECSPERDAY < hi - boundary
886           ? boundary + SECSPERDAY
887           : hi + (hi < TIME_T_MAX));
888   if (SECSPERDAY < boundary - lo) {
889     lo = boundary - SECSPERDAY;
890     lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
891   }
892   if (lotmp)
893     localtm[old] = *lotmp;
894   else
895     localtm[old].tm_sec = -1;
896   if (! my_gmtime_r(&lo, &gmtm[old]))
897     gmtm[old].tm_sec = -1;
898 
899   /* Search sequentially for definedness transitions.  Although this
900      could be sped up by refining 'hunt' to search for either
901      localtime or gmtime definedness transitions, it hardly seems
902      worth the trouble.  */
903   for (t = lo + 1; t < hi; t++) {
904     bool new = !old;
905     if (! my_localtime_rz(tz, &t, &localtm[new]))
906       localtm[new].tm_sec = -1;
907     if (! my_gmtime_r(&t, &gmtm[new]))
908       gmtm[new].tm_sec = -1;
909     if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
910           | ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
911       show(tz, zone, t - 1, true);
912       show(tz, zone, t, true);
913     }
914     old = new;
915   }
916 }
917 
918 /* On pre-C99 platforms, a snprintf substitute good enough for us.  */
919 #if !HAVE_SNPRINTF
920 # include <stdarg.h>
921 ATTRIBUTE_FORMAT((printf, 3, 4)) static int
922 my_snprintf(char *s, size_t size, char const *format, ...)
923 {
924   int n;
925   va_list args;
926   char const *arg;
927   size_t arglen, slen;
928   char buf[1024];
929   va_start(args, format);
930   if (strcmp(format, "%s") == 0) {
931     arg = va_arg(args, char const *);
932     arglen = strlen(arg);
933   } else {
934     n = vsprintf(buf, format, args);
935     if (n < 0) {
936       va_end(args);
937       return n;
938     }
939     arg = buf;
940     arglen = n;
941   }
942   slen = arglen < size ? arglen : size - 1;
943   memcpy(s, arg, slen);
944   s[slen] = '\0';
945   n = arglen <= INT_MAX ? arglen : -1;
946   va_end(args);
947   return n;
948 }
949 # define snprintf my_snprintf
950 #endif
951 
952 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
953    Use ISO 8601 format +HH:MM:SS.  Omit :SS if SS is zero, and omit
954    :MM too if MM is also zero.
955 
956    Return the length of the resulting string.  If the string does not
957    fit, return the length that the string would have been if it had
958    fit; do not overrun the output buffer.  */
959 static int
960 format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
961 {
962   int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
963   return (ss
964             ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
965             : mm
966             ? snprintf(buf, size, "%02d:%02d", hh, mm)
967             : snprintf(buf, size, "%02d", hh));
968 }
969 
970 /* Store into BUF, of size SIZE, a formatted UT offset for the
971    localtime *TM corresponding to time T.  Use ISO 8601 format
972    +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
973    format -00 for unknown UT offsets.  If the hour needs more than
974    two digits to represent, extend the length of HH as needed.
975    Otherwise, omit SS if SS is zero, and omit MM too if MM is also
976    zero.
977 
978    Return the length of the resulting string, or -1 if the result is
979    not representable as a string.  If the string does not fit, return
980    the length that the string would have been if it had fit; do not
981    overrun the output buffer.  */
982 static int
983 format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
984 {
985   long off = gmtoff(tm, &t, NULL);
986   char sign = ((off < 0
987                     || (off == 0
988                         && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
989                  ? '-' : '+');
990   long hh;
991   int mm, ss;
992   if (off < 0)
993     {
994       if (off == LONG_MIN)
995           return -1;
996       off = -off;
997     }
998   ss = off % 60;
999   mm = off / 60 % 60;
1000   hh = off / 60 / 60;
1001   return (ss || 100 <= hh
1002             ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
1003             : mm
1004             ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
1005             : snprintf(buf, size, "%c%02ld", sign, hh));
1006 }
1007 
1008 /* Store into BUF (of size SIZE) a quoted string representation of P.
1009    If the representation's length is less than SIZE, return the
1010    length; the representation is not null terminated.  Otherwise
1011    return SIZE, to indicate that BUF is too small.  */
1012 static ptrdiff_t
1013 format_quoted_string(char *buf, ptrdiff_t size, char const *p)
1014 {
1015   char *b = buf;
1016   ptrdiff_t s = size;
1017   if (!s)
1018     return size;
1019   *b++ = '"', s--;
1020   for (;;) {
1021     char c = *p++;
1022     if (s <= 1)
1023       return size;
1024     switch (c) {
1025     default: *b++ = c, s--; continue;
1026     case '\0': *b++ = '"', s--; return size - s;
1027     case '"': case '\\': break;
1028     case ' ': c = 's'; break;
1029     case '\f': c = 'f'; break;
1030     case '\n': c = 'n'; break;
1031     case '\r': c = 'r'; break;
1032     case '\t': c = 't'; break;
1033     case '\v': c = 'v'; break;
1034     }
1035     *b++ = '\\', *b++ = c, s -= 2;
1036   }
1037 }
1038 
1039 /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
1040    TM is the broken-down time, T the seconds count, AB the time zone
1041    abbreviation, and ZONE_NAME the zone name.  Return true if
1042    successful, false if the output would require more than SIZE bytes.
1043    TIME_FMT uses the same format that strftime uses, with these
1044    additions:
1045 
1046    %f zone name
1047    %L local time as per format_local_time
1048    %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
1049       and D is the isdst flag; except omit D if it is zero, omit %Z if
1050       it equals U, quote and escape %Z if it contains nonalphabetics,
1051       and omit any trailing tabs.  */
1052 
1053 static bool
1054 istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
1055             struct tm const *tm, time_t t, char const *ab, char const *zone_name)
1056 {
1057   char *b = buf;
1058   ptrdiff_t s = size;
1059   char const *f = time_fmt, *p;
1060 
1061   for (p = f; ; p++)
1062     if (*p == '%' && p[1] == '%')
1063       p++;
1064     else if (!*p
1065                || (*p == '%'
1066                      && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
1067       ptrdiff_t formatted_len;
1068       ptrdiff_t f_prefix_len = p - f;
1069       ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
1070       char fbuf[100];
1071       bool oversized = (ptrdiff_t)sizeof fbuf <= f_prefix_copy_size;
1072       char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
1073       memcpy(f_prefix_copy, f, f_prefix_len);
1074       strcpy(f_prefix_copy + f_prefix_len, "X");
1075       formatted_len = strftime(b, s, f_prefix_copy, tm);
1076       if (oversized)
1077           free(f_prefix_copy);
1078       if (formatted_len == 0)
1079           return false;
1080       formatted_len--;
1081       b += formatted_len, s -= formatted_len;
1082       if (!*p++)
1083           break;
1084       switch (*p) {
1085       case 'f':
1086           formatted_len = format_quoted_string(b, s, zone_name);
1087           break;
1088       case 'L':
1089           formatted_len = format_local_time(b, s, tm);
1090           break;
1091       case 'Q':
1092           {
1093             bool show_abbr;
1094             int offlen = format_utc_offset(b, s, tm, t);
1095             if (! (0 <= offlen && offlen < s))
1096               return false;
1097             show_abbr = strcmp(b, ab) != 0;
1098             b += offlen, s -= offlen;
1099             if (show_abbr) {
1100               char const *abp;
1101               ptrdiff_t len;
1102               if (s <= 1)
1103                 return false;
1104               *b++ = '\t', s--;
1105               for (abp = ab; is_alpha(*abp); abp++)
1106                 continue;
1107               len = (!*abp && *ab
1108                        ? snprintf(b, s, "%s", ab)
1109                        : format_quoted_string(b, s, ab));
1110               if (s <= len)
1111                 return false;
1112               b += len, s -= len;
1113             }
1114             formatted_len
1115               = (tm->tm_isdst
1116                  ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
1117                  : 0);
1118           }
1119           break;
1120       }
1121       if (s <= formatted_len)
1122           return false;
1123       b += formatted_len, s -= formatted_len;
1124       f = p + 1;
1125     }
1126   *b = '\0';
1127   return true;
1128 }
1129 
1130 /* Show a time transition.  */
1131 static void
1132 showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
1133             char const *zone_name)
1134 {
1135   if (!tm) {
1136     printf(tformat(), t);
1137     putchar('\n');
1138   } else {
1139     char stackbuf[1000];
1140     ptrdiff_t size = sizeof stackbuf;
1141     char *buf = stackbuf;
1142     char *bufalloc = NULL;
1143     while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
1144       size = sumsize(size, size);
1145       free(bufalloc);
1146       buf = bufalloc = xmalloc(size);
1147     }
1148     puts(buf);
1149     free(bufalloc);
1150   }
1151 }
1152 
1153 static const char *
1154 abbr(struct tm const *tmp)
1155 {
1156 #ifdef TM_ZONE
1157           return tmp->TM_ZONE;
1158 #else
1159 # if HAVE_TZNAME
1160           if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
1161             return tzname[0 < tmp->tm_isdst];
1162 # endif
1163           return "";
1164 #endif
1165 }
1166 
1167 /*
1168 ** The code below can fail on certain theoretical systems;
1169 ** it works on all known real-world systems as of 2022-01-25.
1170 */
1171 
1172 static const char *
1173 tformat(void)
1174 {
1175 #if HAVE__GENERIC
1176           /* C11-style _Generic is more likely to return the correct
1177              format when distinct types have the same size.  */
1178           char const *fmt =
1179             _Generic(+ (time_t) 0,
1180                        int: "%d", long: "%ld", long long: "%lld",
1181                        unsigned: "%u", unsigned long: "%lu",
1182                        unsigned long long: "%llu",
1183                        default: NULL);
1184           if (fmt)
1185             return fmt;
1186           fmt = _Generic((time_t) 0,
1187                            intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX,
1188                            default: NULL);
1189           if (fmt)
1190             return fmt;
1191 #endif
1192           if (0 > (time_t) -1) {                  /* signed */
1193                     if (sizeof(time_t) == sizeof(intmax_t))
1194                               return "%"PRIdMAX;
1195                     if (sizeof(time_t) > sizeof(long))
1196                               return "%lld";
1197                     if (sizeof(time_t) > sizeof(int))
1198                               return "%ld";
1199                     return "%d";
1200           }
1201 #ifdef PRIuMAX
1202           if (sizeof(time_t) == sizeof(uintmax_t))
1203                     return "%"PRIuMAX;
1204 #endif
1205           if (sizeof(time_t) > sizeof(unsigned long))
1206                     return "%llu";
1207           if (sizeof(time_t) > sizeof(unsigned int))
1208                     return "%lu";
1209           return "%u";
1210 }
1211 
1212 static void
1213 dumptime(const struct tm *timeptr)
1214 {
1215           static const char   wday_name[][4] = {
1216                     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1217           };
1218           static const char   mon_name[][4] = {
1219                     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1220                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1221           };
1222           int                 lead;
1223           int                 trail;
1224           int DIVISOR = 10;
1225 
1226           /*
1227           ** The packaged localtime_rz and gmtime_r never put out-of-range
1228           ** values in tm_wday or tm_mon, but since this code might be compiled
1229           ** with other (perhaps experimental) versions, paranoia is in order.
1230           */
1231           printf("%s %s%3d %.2d:%.2d:%.2d ",
1232                     ((0 <= timeptr->tm_wday
1233                       && timeptr->tm_wday < (int) (sizeof wday_name / sizeof wday_name[0]))
1234                      ? wday_name[timeptr->tm_wday] : "???"),
1235                     ((0 <= timeptr->tm_mon
1236                       && timeptr->tm_mon < (int) (sizeof mon_name / sizeof mon_name[0]))
1237                      ? mon_name[timeptr->tm_mon] : "???"),
1238                     timeptr->tm_mday, timeptr->tm_hour,
1239                     timeptr->tm_min, timeptr->tm_sec);
1240           trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
1241           lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
1242                     trail / DIVISOR;
1243           trail %= DIVISOR;
1244           if (trail < 0 && lead > 0) {
1245                     trail += DIVISOR;
1246                     --lead;
1247           } else if (lead < 0 && trail > 0) {
1248                     trail -= DIVISOR;
1249                     ++lead;
1250           }
1251           if (lead == 0)
1252                     printf("%d", trail);
1253           else      printf("%d%d", lead, ((trail < 0) ? -trail : trail));
1254 }
1255