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