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