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