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