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