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