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