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