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