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