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