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