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