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