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