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