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