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