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