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