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