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