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