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