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