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