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