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