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