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