zdump.c revision 1.61 1 /* $NetBSD: zdump.c,v 1.61 2023/01/15 18:12:37 christos Exp $ */
2 /* Dump time zone data in a textual format. */
3
4 /*
5 ** This file is in the public domain, so clarified as of
6 ** 2009-05-17 by Arthur David Olson.
7 */
8
9 #include <sys/cdefs.h>
10 #ifndef lint
11 __RCSID("$NetBSD: zdump.c,v 1.61 2023/01/15 18:12:37 christos Exp $");
12 #endif /* !defined lint */
13
14 #ifndef NETBSD_INSPIRED
15 # define NETBSD_INSPIRED 1
16 #endif
17
18 #include <err.h>
19 #include "private.h"
20 #include <stdio.h>
21
22 #ifndef HAVE_SNPRINTF
23 # define HAVE_SNPRINTF (199901 <= __STDC_VERSION__)
24 #endif
25
26 #ifndef HAVE_LOCALTIME_R
27 # define HAVE_LOCALTIME_R 1
28 #endif
29
30 #ifndef HAVE_LOCALTIME_RZ
31 # ifdef TM_ZONE
32 # define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
33 # else
34 # define HAVE_LOCALTIME_RZ 0
35 # endif
36 #endif
37
38 #ifndef HAVE_TZSET
39 # define HAVE_TZSET 1
40 #endif
41
42 #ifndef ZDUMP_LO_YEAR
43 # define ZDUMP_LO_YEAR (-500)
44 #endif /* !defined ZDUMP_LO_YEAR */
45
46 #ifndef ZDUMP_HI_YEAR
47 # define ZDUMP_HI_YEAR 2500
48 #endif /* !defined ZDUMP_HI_YEAR */
49
50 #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
51 #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
52 #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
53 + SECSPERLYEAR * (intmax_t) (100 - 3))
54
55 /*
56 ** True if SECSPER400YEARS is known to be representable as an
57 ** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
58 ** even if SECSPER400YEARS is representable, because when that happens
59 ** the code merely runs a bit more slowly, and this slowness doesn't
60 ** occur on any practical platform.
61 */
62 enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
63
64 #if HAVE_GETTEXT
65 # include <locale.h> /* for setlocale */
66 #endif /* HAVE_GETTEXT */
67
68 #if ! HAVE_LOCALTIME_RZ
69 # undef timezone_t
70 # define timezone_t char **
71 #endif
72
73 #if !HAVE_POSIX_DECLS
74 extern int getopt(int argc, char * const argv[],
75 const char * options);
76 extern char * optarg;
77 extern int optind;
78 #endif
79
80 /* The minimum and maximum finite time values. */
81 enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
82 static time_t absolute_min_time =
83 ((time_t) -1 < 0
84 ? (- ((time_t) ~ (time_t) 0 < 0)
85 - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
86 : 0);
87 static time_t absolute_max_time =
88 ((time_t) -1 < 0
89 ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
90 : -1);
91 static size_t longest;
92 static char const *progname;
93 static bool warned;
94 static bool errout;
95
96 static char const *abbr(struct tm const *);
97 ATTRIBUTE_REPRODUCIBLE static intmax_t delta(struct tm *, struct tm *);
98 static void dumptime(struct tm const *);
99 static time_t hunt(timezone_t, time_t, time_t, bool);
100 static void show(timezone_t, char *, time_t, bool);
101 static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
102 static void showtrans(char const *, struct tm const *, time_t, char const *,
103 char const *);
104 static const char *tformat(void);
105 ATTRIBUTE_REPRODUCIBLE static time_t yeartot(intmax_t);
106
107 /* Is C an ASCII digit? */
108 static bool
109 is_digit(char c)
110 {
111 return '0' <= c && c <= '9';
112 }
113
114 /* Is A an alphabetic character in the C locale? */
115 static bool
116 is_alpha(char a)
117 {
118 switch (a) {
119 default:
120 return false;
121 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
122 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
123 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
124 case 'V': case 'W': case 'X': case 'Y': case 'Z':
125 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
126 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
127 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
128 case 'v': case 'w': case 'x': case 'y': case 'z':
129 return true;
130 }
131 }
132
133 ATTRIBUTE_NORETURN static void
134 size_overflow(void)
135 {
136 fprintf(stderr, _("%s: size overflow\n"), progname);
137 exit(EXIT_FAILURE);
138 }
139
140 /* Return A + B, exiting if the result would overflow either ptrdiff_t
141 or size_t. */
142 ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
143 sumsize(size_t a, size_t b)
144 {
145 #ifdef ckd_add
146 ptrdiff_t sum;
147 if (!ckd_add(&sum, a, b) && sum <= (ptrdiff_t)min(PTRDIFF_MAX, SIZE_MAX))
148 return sum;
149 #else
150 ptrdiff_t sum_max = (ptrdiff_t)min(PTRDIFF_MAX, SIZE_MAX);
151 if (a <= sum_max && b <= sum_max - a)
152 return a + b;
153 #endif
154 size_overflow();
155 }
156
157 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
158 on failure. SIZE should be nonzero. */
159 ATTRIBUTE_MALLOC static void *
160 xmalloc(size_t size)
161 {
162 void *p = malloc(size);
163 if (!p) {
164 fprintf(stderr, _("%s: Memory exhausted\n"), progname);
165 exit(EXIT_FAILURE);
166 }
167 return p;
168 }
169
170 #if ! HAVE_TZSET
171 # undef tzset
172 # define tzset zdump_tzset
173 static void tzset(void) { }
174 #endif
175
176 /* Assume gmtime_r works if localtime_r does.
177 A replacement localtime_r is defined below if needed. */
178 #if ! HAVE_LOCALTIME_R
179
180 # undef gmtime_r
181 # define gmtime_r zdump_gmtime_r
182
183 static struct tm *
184 gmtime_r(time_t *tp, struct tm *tmp)
185 {
186 struct tm *r = gmtime(tp);
187 if (r) {
188 *tmp = *r;
189 r = tmp;
190 }
191 return r;
192 }
193
194 #endif
195
196 /* Platforms with TM_ZONE don't need tzname, so they can use the
197 faster localtime_rz or localtime_r if available. */
198
199 #if defined TM_ZONE && HAVE_LOCALTIME_RZ
200 # define USE_LOCALTIME_RZ true
201 #else
202 # define USE_LOCALTIME_RZ false
203 #endif
204
205 #if ! USE_LOCALTIME_RZ
206
207 # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
208 # undef localtime_r
209 # define localtime_r zdump_localtime_r
210 static struct tm *
211 localtime_r(time_t *tp, struct tm *tmp)
212 {
213 struct tm *r = localtime(tp);
214 if (r) {
215 *tmp = *r;
216 r = tmp;
217 }
218 return r;
219 }
220 # endif
221
222 # undef localtime_rz
223 # define localtime_rz zdump_localtime_rz
224 static struct tm *
225 localtime_rz(timezone_t rz, time_t *tp, struct tm *tmp)
226 {
227 return localtime_r(tp, tmp);
228 }
229
230 # ifdef TYPECHECK
231 # undef mktime_z
232 # define mktime_z zdump_mktime_z
233 static time_t
234 mktime_z(timezone_t tz, struct tm *tmp)
235 {
236 return mktime(tmp);
237 }
238 # endif
239
240 # undef tzalloc
241 # undef tzfree
242 # define tzalloc zdump_tzalloc
243 # define tzfree zdump_tzfree
244
245 static timezone_t
246 tzalloc(char const *val)
247 {
248 # if HAVE_SETENV
249 if (setenv("TZ", val, 1) != 0) {
250 perror("setenv");
251 exit(EXIT_FAILURE);
252 }
253 tzset();
254 return &optarg; /* Any valid non-null char ** will do. */
255 # else
256 enum { TZeqlen = 3 };
257 static char const TZeq[TZeqlen] = "TZ=";
258 static ptrdiff_t fakeenv0size;
259 void *freeable = NULL;
260 char **env = fakeenv, **initial_environ;
261 size_t valsize = strlen(val) + 1;
262 if (fakeenv0size < valsize) {
263 char **e = environ, **to;
264 ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */
265
266 while (*e++) {
267 # ifdef ckd_add
268 if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
269 || SIZE_MAX < initial_envptrs)
270 size_overflow();
271 # else
272 if (initial_nenvptrs == (ptrdiff_t)(min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ))
273 size_overflow();
274 initial_nenvptrs++;
275 # endif
276 + }
277 fakeenv0size = sumsize(valsize, valsize);
278 fakeenv0size = max(fakeenv0size, 64);
279 freeable = env;
280 fakeenv = env =
281 xmalloc(sumsize(sumsize(sizeof *environ,
282 initial_nenvptrs * sizeof *environ),
283 sumsize(TZeqlen, fakeenv0size)));
284 to = env + 1;
285 for (e = environ; (*to = *e); e++)
286 to += strncmp(*e, TZeq, TZeqlen) != 0;
287 env[0] = memcpy(to + 1, TZeq, TZeqlen);
288 }
289 memcpy(env[0] + TZeqlen, val, valsize);
290 initial_environ = environ;
291 environ = env;
292 tzset();
293 free(freeable);
294 return initial_environ;
295 # endif
296 }
297
298 static void
299 tzfree(timezone_t initial_environ)
300 {
301 # if !HAVE_SETENV
302 environ = initial_environ;
303 tzset();
304 # endif
305 }
306 #endif /* ! USE_LOCALTIME_RZ */
307
308 /* A UT time zone, and its initializer. */
309 static timezone_t gmtz;
310 static void
311 gmtzinit(void)
312 {
313 if (USE_LOCALTIME_RZ) {
314 /* Try "GMT" first to find out whether this is one of the rare
315 platforms where time_t counts leap seconds; this works due to
316 the "Zone GMT 0 - GMT" line in the "etcetera" file. If "GMT"
317 fails, fall back on "GMT0" which might be similar due to the
318 "Link GMT GMT0" line in the "backward" file, and which
319 should work on all POSIX platforms. The rest of zdump does not
320 use the "GMT" abbreviation that comes from this setting, so it
321 is OK to use "GMT" here rather than the more-modern "UTC" which
322 would not work on platforms that omit the "backward" file. */
323 gmtz = tzalloc("GMT");
324 if (!gmtz) {
325 static char const gmt0[] = "GMT0";
326 gmtz = tzalloc(gmt0);
327 if (!gmtz) {
328 err(EXIT_FAILURE, "Cannot create %s", gmt0);
329 }
330 }
331 }
332 }
333
334 /* Convert *TP to UT, storing the broken-down time into *TMP.
335 Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
336 except typically faster if USE_LOCALTIME_RZ. */
337 static struct tm *
338 my_gmtime_r(time_t *tp, struct tm *tmp)
339 {
340 return USE_LOCALTIME_RZ ?
341 localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
342 }
343
344 #ifndef TYPECHECK
345 #define my_localtime_rz localtime_rz
346 #else /* !defined TYPECHECK */
347 static struct tm *
348 my_localtime_rz(timezone_t tz, const time_t *tp, struct tm *tmp)
349 {
350 tmp = localtime_rz(tz, tp, tmp);
351 if (tmp) {
352 struct tm tm;
353 time_t t;
354
355 tm = *tmp;
356 t = mktime_z(tz, &tm);
357 if (t != *tp) {
358 (void) fflush(stdout);
359 (void) fprintf(stderr, "\n%s: ", progname);
360 (void) fprintf(stderr, tformat(), *tp);
361 (void) fprintf(stderr, " ->");
362 (void) fprintf(stderr, " year=%d", tmp->tm_year);
363 (void) fprintf(stderr, " mon=%d", tmp->tm_mon);
364 (void) fprintf(stderr, " mday=%d", tmp->tm_mday);
365 (void) fprintf(stderr, " hour=%d", tmp->tm_hour);
366 (void) fprintf(stderr, " min=%d", tmp->tm_min);
367 (void) fprintf(stderr, " sec=%d", tmp->tm_sec);
368 (void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
369 (void) fprintf(stderr, " -> ");
370 (void) fprintf(stderr, tformat(), t);
371 (void) fprintf(stderr, "\n");
372 errout = true;
373 }
374 }
375 return tmp;
376 }
377 #endif /* !defined TYPECHECK */
378
379 static void
380 abbrok(const char *const abbrp, const char *const zone)
381 {
382 const char *cp;
383 const char *wp;
384
385 if (warned)
386 return;
387 cp = abbrp;
388 while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
389 ++cp;
390 if (*cp)
391 wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
392 else if (cp - abbrp < 3)
393 wp = _("has fewer than 3 characters");
394 else if (cp - abbrp > 6)
395 wp = _("has more than 6 characters");
396 else
397 return;
398 (void) fflush(stdout);
399 (void) fprintf(stderr,
400 _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
401 progname, zone, abbrp, wp);
402 warned = errout = true;
403 }
404
405 /* Return a time zone abbreviation. If the abbreviation needs to be
406 saved, use *BUF (of size *BUFALLOC) to save it, and return the
407 abbreviation in the possibly-reallocated *BUF. Otherwise, just
408 return the abbreviation. Get the abbreviation from TMP.
409 Exit on memory allocation failure. */
410 static char const *
411 saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
412 {
413 char const *ab = abbr(tmp);
414 if (HAVE_LOCALTIME_RZ)
415 return ab;
416 else {
417 size_t ablen = strlen(ab);
418 if (*bufalloc <= (ptrdiff_t)ablen) {
419 free(*buf);
420
421 /* Make the new buffer at least twice as long as the
422 old, to avoid O(N**2) behavior on repeated calls. */
423 *bufalloc = sumsize(*bufalloc, ablen + 1);
424 *buf = xmalloc(*bufalloc);
425 }
426 return strcpy(*buf, ab);
427 }
428 }
429
430 static void
431 close_file(FILE *stream)
432 {
433 char const *e = (ferror(stream) ? _("I/O error")
434 : fclose(stream) != 0 ? strerror(errno) : NULL);
435 if (e) {
436 errx(EXIT_FAILURE, "%s", e);
437 }
438 }
439
440 __dead static void
441 usage(FILE *const stream, const int status)
442 {
443 (void) fprintf(stream,
444 _("%s: usage: %s OPTIONS TIMEZONE ...\n"
445 "Options include:\n"
446 " -c [L,]U Start at year L (default -500), end before year U (default 2500)\n"
447 " -t [L,]U Start at time L, end before time U (in seconds since 1970)\n"
448 " -i List transitions briefly (format is experimental)\n" \
449 " -v List transitions verbosely\n"
450 " -V List transitions a bit less verbosely\n"
451 " --help Output this help\n"
452 " --version Output version info\n"
453 "\n"
454 "Report bugs to %s.\n"),
455 progname, progname, REPORT_BUGS_TO);
456 if (status == EXIT_SUCCESS)
457 close_file(stream);
458 exit(status);
459 }
460
461 int
462 main(int argc, char *argv[])
463 {
464 /* These are static so that they're initially zero. */
465 static char * abbrev;
466 static ptrdiff_t abbrevsize;
467
468 int i;
469 bool vflag;
470 bool Vflag;
471 char * cutarg;
472 char * cuttimes;
473 time_t cutlotime;
474 time_t cuthitime;
475 time_t now;
476 bool iflag = false;
477
478 cutlotime = absolute_min_time;
479 cuthitime = absolute_max_time;
480 #if HAVE_GETTEXT
481 (void) setlocale(LC_ALL, "");
482 # ifdef TZ_DOMAINDIR
483 (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
484 # endif /* defined TEXTDOMAINDIR */
485 (void) 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 (void) 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 intmax_t cutloyear = ZDUMP_LO_YEAR;
518 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 (void) 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 longest = 0;
577 for (i = optind; i < argc; i++) {
578 size_t arglen = strlen(argv[i]);
579 if (longest < arglen)
580 longest = min(arglen, INT_MAX);
581 }
582
583 for (i = optind; i < argc; ++i) {
584 timezone_t tz = tzalloc(argv[i]);
585 char const *ab;
586 time_t t;
587 struct tm tm, newtm;
588 bool tm_ok;
589
590 if (!tz) {
591 errx(EXIT_FAILURE, "%s", argv[i]);
592 }
593 if (now) {
594 show(tz, argv[i], now, false);
595 tzfree(tz);
596 continue;
597 }
598 warned = false;
599 t = absolute_min_time;
600 if (! (iflag | Vflag)) {
601 show(tz, argv[i], t, true);
602 if (my_localtime_rz(tz, &t, &tm) == NULL
603 && t < cutlotime) {
604 time_t newt = cutlotime;
605 if (my_localtime_rz(tz, &newt, &newtm) != NULL)
606 showextrema(tz, argv[i], t, NULL, newt);
607 }
608 }
609 if (t + 1 < cutlotime)
610 t = cutlotime - 1;
611 tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
612 if (tm_ok) {
613 ab = saveabbr(&abbrev, &abbrevsize, &tm);
614 if (iflag) {
615 showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
616 showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
617 }
618 } else
619 ab = NULL;
620 while (t < cuthitime - 1) {
621 time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
622 && t + SECSPERDAY / 2 < cuthitime - 1)
623 ? t + SECSPERDAY / 2
624 : cuthitime - 1);
625 struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
626 bool newtm_ok = newtmp != NULL;
627 if (tm_ok != newtm_ok
628 || (ab && (delta(&newtm, &tm) != newt - t
629 || newtm.tm_isdst != tm.tm_isdst
630 || strcmp(abbr(&newtm), ab) != 0))) {
631 newt = hunt(tz, t, newt, false);
632 newtmp = localtime_rz(tz, &newt, &newtm);
633 newtm_ok = newtmp != NULL;
634 if (iflag)
635 showtrans("%Y-%m-%d\t%L\t%Q",
636 newtmp, newt, newtm_ok ?
637 abbr(&newtm) : NULL, argv[i]);
638 else {
639 show(tz, argv[i], newt - 1, true);
640 show(tz, argv[i], newt, true);
641 }
642 }
643 t = newt;
644 tm_ok = newtm_ok;
645 if (newtm_ok) {
646 ab = saveabbr(&abbrev, &abbrevsize, &newtm);
647 tm = newtm;
648 }
649 }
650 if (! (iflag | Vflag)) {
651 time_t newt = absolute_max_time;
652 t = cuthitime;
653 if (t < newt) {
654 struct tm *tmp = my_localtime_rz(tz, &t, &tm);
655 if (tmp != NULL
656 && my_localtime_rz(tz, &newt, &newtm) == NULL)
657 showextrema(tz, argv[i], t, tmp, newt);
658 }
659 show(tz, argv[i], absolute_max_time, true);
660 }
661 tzfree(tz);
662 }
663 close_file(stdout);
664 if (errout && (ferror(stderr) || fclose(stderr) != 0))
665 return EXIT_FAILURE;
666 return EXIT_SUCCESS;
667 }
668
669 static time_t
670 yeartot(intmax_t y)
671 {
672 intmax_t myy, seconds, years;
673 time_t t;
674
675 myy = EPOCH_YEAR;
676 t = 0;
677 while (myy < y) {
678 if (SECSPER400YEARS_FITS && 400 <= y - myy) {
679 intmax_t diff400 = (y - myy) / 400;
680 if (INTMAX_MAX / SECSPER400YEARS < diff400)
681 return absolute_max_time;
682 seconds = diff400 * SECSPER400YEARS;
683 years = diff400 * 400;
684 } else {
685 seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
686 years = 1;
687 }
688 myy += years;
689 if (t > absolute_max_time - seconds)
690 return absolute_max_time;
691 t += seconds;
692 }
693 while (y < myy) {
694 if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
695 intmax_t diff400 = (myy - y) / 400;
696 if (INTMAX_MAX / SECSPER400YEARS < diff400)
697 return absolute_min_time;
698 seconds = diff400 * SECSPER400YEARS;
699 years = diff400 * 400;
700 } else {
701 seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
702 years = 1;
703 }
704 myy -= years;
705 if (t < absolute_min_time + seconds)
706 return absolute_min_time;
707 t -= seconds;
708 }
709 return t;
710 }
711
712 /* Search for a discontinuity in timezone TZ, in the
713 timestamps ranging from LOT through HIT. LOT and HIT disagree
714 about some aspect of timezone. If ONLY_OK, search only for
715 definedness changes, i.e., localtime succeeds on one side of the
716 transition but fails on the other side. Return the timestamp just
717 before the transition from LOT's settings. */
718
719 static time_t
720 hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
721 {
722 static char * loab;
723 static ptrdiff_t loabsize;
724 struct tm lotm;
725 struct tm tm;
726
727 /* Convert LOT into a broken-down time here, even though our
728 caller already did that. On platforms without TM_ZONE,
729 tzname may have been altered since our caller broke down
730 LOT, and tzname needs to be changed back. */
731 bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
732 bool tm_ok;
733 char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
734
735 for ( ; ; ) {
736 /* T = average of LOT and HIT, rounding down.
737 Avoid overflow, even on oddball C89 platforms
738 where / rounds down and TIME_T_MIN == -TIME_T_MAX
739 so lot / 2 + hit / 2 might overflow. */
740 time_t t = (lot / 2
741 - ((lot % 2 + hit % 2) < 0)
742 + ((lot % 2 + hit % 2) == 2)
743 + hit / 2);
744 if (t == lot)
745 break;
746 tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
747 if (lotm_ok == tm_ok
748 && (only_ok
749 || (ab && tm.tm_isdst == lotm.tm_isdst
750 && delta(&tm, &lotm) == t - lot
751 && strcmp(abbr(&tm), ab) == 0))) {
752 lot = t;
753 if (tm_ok)
754 lotm = tm;
755 } else hit = t;
756 }
757 return hit;
758 }
759
760 /*
761 ** Thanks to Paul Eggert for logic used in delta_nonneg.
762 */
763
764 static intmax_t
765 delta_nonneg(struct tm *newp, struct tm *oldp)
766 {
767 intmax_t oldy = oldp->tm_year;
768 int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
769 intmax_t sec = SECSPERREPEAT, result = cycles * sec;
770 int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
771 for ( ; tmy < newp->tm_year; ++tmy)
772 result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
773 result += newp->tm_yday - oldp->tm_yday;
774 result *= HOURSPERDAY;
775 result += newp->tm_hour - oldp->tm_hour;
776 result *= MINSPERHOUR;
777 result += newp->tm_min - oldp->tm_min;
778 result *= SECSPERMIN;
779 result += newp->tm_sec - oldp->tm_sec;
780 return result;
781 }
782
783 static intmax_t
784 delta(struct tm *newp, struct tm *oldp)
785 {
786 return (newp->tm_year < oldp->tm_year
787 ? -delta_nonneg(oldp, newp)
788 : delta_nonneg(newp, oldp));
789 }
790
791 #ifndef TM_GMTOFF
792 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
793 Assume A and B differ by at most one year. */
794 static int
795 adjusted_yday(struct tm const *a, struct tm const *b)
796 {
797 int yday = a->tm_yday;
798 if (b->tm_year < a->tm_year)
799 yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
800 return yday;
801 }
802 #endif
803
804 /* If A is the broken-down local time and B the broken-down UT for
805 the same instant, return A's UT offset in seconds, where positive
806 offsets are east of Greenwich. On failure, return LONG_MIN.
807
808 If T is nonnull, *T is the timestamp that corresponds to A; call
809 my_gmtime_r and use its result instead of B. Otherwise, B is the
810 possibly nonnull result of an earlier call to my_gmtime_r. */
811 static long
812 gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
813 ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
814 {
815 #ifdef TM_GMTOFF
816 return a->TM_GMTOFF;
817 #else
818 struct tm tm;
819 if (t)
820 b = my_gmtime_r(t, &tm);
821 if (! b)
822 return LONG_MIN;
823 else {
824 int ayday = adjusted_yday(a, b);
825 int byday = adjusted_yday(b, a);
826 int days = ayday - byday;
827 long hours = a->tm_hour - b->tm_hour + 24 * days;
828 long minutes = a->tm_min - b->tm_min + 60 * hours;
829 long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
830 return seconds;
831 }
832 #endif
833 }
834
835 static void
836 show(timezone_t tz, char *zone, time_t t, bool v)
837 {
838 struct tm * tmp;
839 struct tm * gmtmp;
840 struct tm tm, gmtm;
841
842 (void) printf("%-*s ", (int) longest, zone);
843 if (v) {
844 gmtmp = my_gmtime_r(&t, &gmtm);
845 if (gmtmp == NULL) {
846 printf(tformat(), t);
847 printf(_(" (gmtime failed)"));
848 } else {
849 dumptime(gmtmp);
850 (void) printf(" UT");
851 }
852 (void) printf(" = ");
853 }
854 tmp = my_localtime_rz(tz, &t, &tm);
855 if (tmp == NULL) {
856 printf(tformat(), t);
857 printf(_(" (localtime failed)"));
858 } else {
859 dumptime(tmp);
860 if (*abbr(tmp) != '\0')
861 (void) printf(" %s", abbr(tmp));
862 if (v) {
863 long off = gmtoff(tmp, NULL, gmtmp);
864 (void) printf(" isdst=%d", tmp->tm_isdst);
865 if (off != LONG_MIN)
866 (void) printf(" gmtoff=%ld", off);
867 }
868 }
869 (void) printf("\n");
870 if (tmp != NULL && *abbr(tmp) != '\0')
871 abbrok(abbr(tmp), zone);
872 }
873
874 /* Show timestamps just before and just after a transition between
875 defined and undefined (or vice versa) in either localtime or
876 gmtime. These transitions are for timezone TZ with name ZONE, in
877 the range from LO (with broken-down time LOTMP if that is nonnull)
878 through HI. LO and HI disagree on definedness. */
879
880 static void
881 showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
882 {
883 struct tm localtm[2], gmtm[2];
884 time_t t, boundary = hunt(tz, lo, hi, true);
885 bool old = false;
886 hi = (SECSPERDAY < hi - boundary
887 ? boundary + SECSPERDAY
888 : hi + (hi < TIME_T_MAX));
889 if (SECSPERDAY < boundary - lo) {
890 lo = boundary - SECSPERDAY;
891 lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
892 }
893 if (lotmp)
894 localtm[old] = *lotmp;
895 else
896 localtm[old].tm_sec = -1;
897 if (! my_gmtime_r(&lo, &gmtm[old]))
898 gmtm[old].tm_sec = -1;
899
900 /* Search sequentially for definedness transitions. Although this
901 could be sped up by refining 'hunt' to search for either
902 localtime or gmtime definedness transitions, it hardly seems
903 worth the trouble. */
904 for (t = lo + 1; t < hi; t++) {
905 bool new = !old;
906 if (! my_localtime_rz(tz, &t, &localtm[new]))
907 localtm[new].tm_sec = -1;
908 if (! my_gmtime_r(&t, &gmtm[new]))
909 gmtm[new].tm_sec = -1;
910 if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
911 | ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
912 show(tz, zone, t - 1, true);
913 show(tz, zone, t, true);
914 }
915 old = new;
916 }
917 }
918
919 #if HAVE_SNPRINTF
920 # define my_snprintf snprintf
921 #else
922 # include <stdarg.h>
923
924 /* A substitute for snprintf that is good enough for zdump. */
925 ATTRIBUTE_FORMAT((printf, 3, 4)) static int
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 #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
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 ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
968 : mm
969 ? my_snprintf(buf, size, "%02d:%02d", hh, mm)
970 : my_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
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 ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
1006 : mm
1007 ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
1008 : my_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
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
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 = (ptrdiff_t)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 ? my_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 ? my_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
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 const char *
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 *
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
1216 dumptime(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 int lead;
1226 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 < (int) (sizeof wday_name / sizeof wday_name[0]))
1237 ? wday_name[timeptr->tm_wday] : "???"),
1238 ((0 <= timeptr->tm_mon
1239 && timeptr->tm_mon < (int) (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