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