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