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