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