Annotation of src/lib/libc/time/zdump.c, Revision 1.46
1.46 ! christos 1: /* $NetBSD: zdump.c,v 1.45 2016/10/20 17:41:34 christos Exp $ */
1.17 mlelstv 2: /*
3: ** This file is in the public domain, so clarified as of
4: ** 2009-05-17 by Arthur David Olson.
5: */
1.2 jtc 6:
1.6 christos 7: #include <sys/cdefs.h>
1.1 jtc 8: #ifndef lint
1.46 ! christos 9: __RCSID("$NetBSD: zdump.c,v 1.45 2016/10/20 17:41:34 christos Exp $");
1.1 jtc 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.
1.36 christos 16: ** To do this, compile with -DUSE_LTZ=0 and link without the tz library.
1.1 jtc 17: */
18:
1.36 christos 19: #ifndef NETBSD_INSPIRED
20: # define NETBSD_INSPIRED 1
21: #endif
22: #ifndef USE_LTZ
23: # define USE_LTZ 1
24: #endif
25:
1.46 ! christos 26: #include <err.h>
1.29 christos 27: #include "private.h"
1.36 christos 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
1.27 christos 44:
1.17 mlelstv 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 */
1.1 jtc 52:
53: #ifndef MAX_STRING_LENGTH
54: #define MAX_STRING_LENGTH 1024
55: #endif /* !defined MAX_STRING_LENGTH */
56:
1.17 mlelstv 57: #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
58: #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
1.31 christos 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 };
1.17 mlelstv 70:
71: #if HAVE_GETTEXT
1.46 ! christos 72: #include <locale.h> /* for setlocale */
1.17 mlelstv 73: #endif /* HAVE_GETTEXT */
1.3 jtc 74:
1.36 christos 75: #if ! HAVE_LOCALTIME_RZ
76: # undef timezone_t
77: # define timezone_t char **
78: #endif
79:
1.44 christos 80: extern char ** environ;
81:
1.43 christos 82: #if !HAVE_POSIX_DECLS
1.17 mlelstv 83: extern int getopt(int argc, char * const argv[],
84: const char * options);
1.1 jtc 85: extern char * optarg;
86: extern int optind;
1.43 christos 87: extern char * tzname[];
88: #endif
1.1 jtc 89:
1.29 christos 90: /* The minimum and maximum finite time values. */
1.41 christos 91: enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 };
1.29 christos 92: static time_t absolute_min_time =
1.31 christos 93: ((time_t) -1 < 0
1.41 christos 94: ? (- ((time_t) ~ (time_t) 0 < 0)
95: - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
1.29 christos 96: : 0);
97: static time_t absolute_max_time =
1.31 christos 98: ((time_t) -1 < 0
1.41 christos 99: ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
1.29 christos 100: : -1);
1.5 jtc 101: static size_t longest;
1.1 jtc 102: static char * progname;
1.36 christos 103: static bool warned;
104: static bool errout;
1.17 mlelstv 105:
1.36 christos 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);
1.44 christos 111: static void showtrans(char const *, struct tm const *, time_t, char const *,
112: char const *);
1.36 christos 113: static const char *tformat(void);
114: static time_t yeartot(intmax_t) ATTRIBUTE_PURE;
1.17 mlelstv 115:
1.42 christos 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:
1.34 christos 119: /* Is A an alphabetic character in the C locale? */
1.36 christos 120: static bool
1.34 christos 121: is_alpha(char a)
122: {
123: switch (a) {
124: default:
1.36 christos 125: return false;
1.34 christos 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':
1.36 christos 134: return true;
1.34 christos 135: }
136: }
137:
1.36 christos 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:
1.44 christos 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:
1.36 christos 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;
1.44 christos 248: env = xmalloc(sumsize(sizeof *environ,
1.36 christos 249: (e - environ) * sizeof *environ));
250: to = 1;
251: for (e = environ; (env[to] = *e); e++)
252: to += strncmp(*e, "TZ=", 3) != 0;
253: }
1.44 christos 254: env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val)));
1.36 christos 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:
1.17 mlelstv 293: #ifndef TYPECHECK
1.36 christos 294: #define my_localtime_rz localtime_rz
1.17 mlelstv 295: #else /* !defined TYPECHECK */
296: static struct tm *
1.36 christos 297: my_localtime_rz(timezone_t tz, const time_t *tp, struct tm *tmp)
1.17 mlelstv 298: {
1.36 christos 299: tmp = localtime_rz(tz, tp, tmp);
300: if (tmp) {
1.17 mlelstv 301: struct tm tm;
1.26 christos 302: time_t t;
1.17 mlelstv 303:
304: tm = *tmp;
1.36 christos 305: t = mktime_z(tz, &tm);
1.31 christos 306: if (t != *tp) {
1.17 mlelstv 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");
1.36 christos 321: errout = true;
1.17 mlelstv 322: }
323: }
324: return tmp;
325: }
326: #endif /* !defined TYPECHECK */
327:
328: static void
1.26 christos 329: abbrok(const char *const abbrp, const char *const zone)
1.17 mlelstv 330: {
1.26 christos 331: const char *cp;
332: const char *wp;
1.17 mlelstv 333:
334: if (warned)
335: return;
336: cp = abbrp;
1.42 christos 337: while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
1.17 mlelstv 338: ++cp;
1.42 christos 339: if (cp - abbrp < 3)
340: wp = _("has fewer than 3 characters");
1.17 mlelstv 341: else if (cp - abbrp > 6)
1.42 christos 342: wp = _("has more than 6 characters");
343: else if (*cp)
344: wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
345: else
1.17 mlelstv 346: return;
347: (void) fflush(stdout);
348: (void) fprintf(stderr,
349: _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
350: progname, zone, abbrp, wp);
1.36 christos 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);
1.44 christos 373: *buf = xmalloc(*bufalloc);
1.36 christos 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: }
1.17 mlelstv 387: }
388:
1.24 joerg 389: __dead static void
1.26 christos 390: usage(FILE *const stream, const int status)
1.17 mlelstv 391: {
392: (void) fprintf(stream,
1.44 christos 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"
1.29 christos 402: "\n"
403: "Report bugs to %s.\n"),
1.44 christos 404: progname, progname, REPORT_BUGS_TO);
1.36 christos 405: if (status == EXIT_SUCCESS)
406: close_file(stream);
1.17 mlelstv 407: exit(status);
408: }
1.1 jtc 409:
410: int
1.26 christos 411: main(int argc, char *argv[])
412: {
1.36 christos 413: /* These are static so that they're initially zero. */
414: static char * abbrev;
415: static size_t abbrevsize;
416:
1.26 christos 417: int i;
1.36 christos 418: bool vflag;
419: bool Vflag;
1.26 christos 420: char * cutarg;
1.29 christos 421: char * cuttimes;
1.26 christos 422: time_t cutlotime;
423: time_t cuthitime;
424: time_t now;
1.44 christos 425: bool iflag = false;
1.1 jtc 426:
1.29 christos 427: cutlotime = absolute_min_time;
428: cuthitime = absolute_max_time;
1.17 mlelstv 429: #if HAVE_GETTEXT
430: (void) setlocale(LC_ALL, "");
1.3 jtc 431: #ifdef TZ_DOMAINDIR
432: (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
1.17 mlelstv 433: #endif /* defined TEXTDOMAINDIR */
1.3 jtc 434: (void) textdomain(TZ_DOMAIN);
1.17 mlelstv 435: #endif /* HAVE_GETTEXT */
1.1 jtc 436: progname = argv[0];
1.14 kleink 437: for (i = 1; i < argc; ++i)
438: if (strcmp(argv[i], "--version") == 0) {
1.28 christos 439: (void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
1.36 christos 440: return EXIT_SUCCESS;
1.17 mlelstv 441: } else if (strcmp(argv[i], "--help") == 0) {
1.22 christos 442: usage(stdout, EXIT_SUCCESS);
1.14 kleink 443: }
1.36 christos 444: vflag = Vflag = false;
1.29 christos 445: cutarg = cuttimes = NULL;
446: for (;;)
1.44 christos 447: switch (getopt(argc, argv, "c:it:vV")) {
1.29 christos 448: case 'c': cutarg = optarg; break;
449: case 't': cuttimes = optarg; break;
1.44 christos 450: case 'i': iflag = true; break;
1.36 christos 451: case 'v': vflag = true; break;
452: case 'V': Vflag = true; break;
1.29 christos 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:
1.44 christos 462: if (iflag | vflag | Vflag) {
1.29 christos 463: intmax_t lo;
464: intmax_t hi;
1.32 christos 465: char *loend, *hiend;
1.30 christos 466: intmax_t cutloyear = ZDUMP_LO_YEAR;
467: intmax_t cuthiyear = ZDUMP_HI_YEAR;
1.17 mlelstv 468: if (cutarg != NULL) {
1.32 christos 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;
1.17 mlelstv 477: cuthiyear = hi;
478: } else {
1.36 christos 479: fprintf(stderr, _("%s: wild -c argument %s\n"),
1.17 mlelstv 480: progname, cutarg);
1.36 christos 481: return EXIT_FAILURE;
1.17 mlelstv 482: }
483: }
1.29 christos 484: if (cutarg != NULL || cuttimes == NULL) {
485: cutlotime = yeartot(cutloyear);
486: cuthitime = yeartot(cuthiyear);
487: }
488: if (cuttimes != NULL) {
1.32 christos 489: lo = strtoimax(cuttimes, &loend, 10);
490: if (cuttimes != loend && !*loend) {
491: hi = lo;
1.29 christos 492: if (hi < cuthitime) {
493: if (hi < absolute_min_time)
494: hi = absolute_min_time;
495: cuthitime = hi;
496: }
1.32 christos 497: } else if (cuttimes != loend && *loend == ','
498: && (hi = strtoimax(loend + 1, &hiend, 10),
499: loend + 1 != hiend && !*hiend)) {
1.29 christos 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);
1.36 christos 514: return EXIT_FAILURE;
1.29 christos 515: }
516: }
1.1 jtc 517: }
1.36 christos 518: gmtzinit();
1.44 christos 519: INITIALIZE (now);
520: if (! (iflag | vflag | Vflag))
521: now = time(NULL);
1.1 jtc 522: longest = 0;
1.36 christos 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: }
1.1 jtc 528:
529: for (i = optind; i < argc; ++i) {
1.36 christos 530: timezone_t tz = tzalloc(argv[i]);
531: char const *ab;
1.44 christos 532: time_t t;
533: struct tm tm, newtm;
534: bool tm_ok;
535:
1.36 christos 536: if (!tz) {
537: errx(EXIT_FAILURE, "%s", argv[i]);
538: }
1.44 christos 539: if (! (iflag | vflag | Vflag)) {
1.36 christos 540: show(tz, argv[i], now, false);
541: tzfree(tz);
1.1 jtc 542: continue;
1.3 jtc 543: }
1.36 christos 544: warned = false;
1.17 mlelstv 545: t = absolute_min_time;
1.44 christos 546: if (! (iflag | Vflag)) {
1.36 christos 547: show(tz, argv[i], t, true);
1.31 christos 548: t += SECSPERDAY;
1.36 christos 549: show(tz, argv[i], t, true);
1.29 christos 550: }
1.17 mlelstv 551: if (t < cutlotime)
552: t = cutlotime;
1.44 christos 553: tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
554: if (tm_ok) {
1.36 christos 555: ab = saveabbr(&abbrev, &abbrevsize, &tm);
1.44 christos 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
1.36 christos 561: ab = NULL;
562: while (t < cuthitime) {
1.44 christos 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)) {
1.36 christos 573: newt = hunt(tz, argv[i], t, newt);
574: newtmp = localtime_rz(tz, &newt, &newtm);
1.44 christos 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: }
1.1 jtc 584: }
585: t = newt;
1.44 christos 586: tm_ok = newtm_ok;
587: if (newtm_ok) {
588: ab = saveabbr(&abbrev, &abbrevsize, &newtm);
589: tm = newtm;
590: }
1.1 jtc 591: }
1.44 christos 592: if (! (iflag | Vflag)) {
1.29 christos 593: t = absolute_max_time;
1.31 christos 594: t -= SECSPERDAY;
1.36 christos 595: show(tz, argv[i], t, true);
1.31 christos 596: t += SECSPERDAY;
1.36 christos 597: show(tz, argv[i], t, true);
1.29 christos 598: }
1.36 christos 599: tzfree(tz);
1.1 jtc 600: }
1.36 christos 601: close_file(stdout);
602: if (errout && (ferror(stderr) || fclose(stderr) != 0))
603: return EXIT_FAILURE;
604: return EXIT_SUCCESS;
1.17 mlelstv 605: }
1.1 jtc 606:
607: static time_t
1.42 christos 608: yeartot(intmax_t y)
1.17 mlelstv 609: {
1.31 christos 610: intmax_t myy, seconds, years;
1.29 christos 611: time_t t;
1.17 mlelstv 612:
613: myy = EPOCH_YEAR;
614: t = 0;
1.31 christos 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 {
1.17 mlelstv 623: seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
1.31 christos 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;
1.17 mlelstv 638: } else {
1.31 christos 639: seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
640: years = 1;
1.17 mlelstv 641: }
1.31 christos 642: myy -= years;
643: if (t < absolute_min_time + seconds)
644: return absolute_min_time;
645: t -= seconds;
1.17 mlelstv 646: }
647: return t;
648: }
649:
650: static time_t
1.36 christos 651: hunt(timezone_t tz, char *name, time_t lot, time_t hit)
1.17 mlelstv 652: {
1.36 christos 653: static char * loab;
654: static size_t loabsize;
655: char const * ab;
1.17 mlelstv 656: time_t t;
657: struct tm lotm;
658: struct tm tm;
1.44 christos 659: bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
660: bool tm_ok;
1.17 mlelstv 661:
1.44 christos 662: if (lotm_ok)
1.36 christos 663: ab = saveabbr(&loab, &loabsize, &lotm);
664: else
665: ab = NULL;
1.17 mlelstv 666: for ( ; ; ) {
1.29 christos 667: time_t diff = hit - lot;
1.17 mlelstv 668: if (diff < 2)
669: break;
670: t = lot;
671: t += diff / 2;
1.1 jtc 672: if (t <= lot)
673: ++t;
674: else if (t >= hit)
675: --t;
1.44 christos 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;
1.1 jtc 685: } else hit = t;
686: }
687: return hit;
688: }
689:
690: /*
1.17 mlelstv 691: ** Thanks to Paul Eggert for logic used in delta.
1.1 jtc 692: */
693:
1.29 christos 694: static intmax_t
1.26 christos 695: delta(struct tm *newp, struct tm *oldp)
1.1 jtc 696: {
1.29 christos 697: intmax_t result;
698: int tmy;
1.1 jtc 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)
1.17 mlelstv 704: result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
1.1 jtc 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:
1.36 christos 715: #ifndef TM_GMTOFF
716: /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
1.39 christos 717: Assume A and B differ by at most one year. */
1.36 christos 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
1.44 christos 730: offsets are east of Greenwich. On failure, return LONG_MIN.
731:
1.46 ! christos 732: If T is nonnull, *T is the timestamp that corresponds to A; call
1.44 christos 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. */
1.36 christos 735: static long
1.44 christos 736: gmtoff(struct tm const *a, time_t *t, struct tm const *b)
1.36 christos 737: {
738: #ifdef TM_GMTOFF
739: return a->TM_GMTOFF;
740: #else
1.44 christos 741: struct tm tm;
742: if (t)
743: b = my_gmtime_r(t, &tm);
1.36 christos 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:
1.1 jtc 758: static void
1.36 christos 759: show(timezone_t tz, char *zone, time_t t, bool v)
1.1 jtc 760: {
1.26 christos 761: struct tm * tmp;
1.36 christos 762: struct tm * gmtmp;
763: struct tm tm, gmtm;
1.1 jtc 764:
1.5 jtc 765: (void) printf("%-*s ", (int) longest, zone);
1.1 jtc 766: if (v) {
1.36 christos 767: gmtmp = my_gmtime_r(&t, &gmtm);
768: if (gmtmp == NULL) {
769: printf(tformat(), t);
1.17 mlelstv 770: } else {
1.36 christos 771: dumptime(gmtmp);
1.31 christos 772: (void) printf(" UT");
1.17 mlelstv 773: }
774: (void) printf(" = ");
775: }
1.36 christos 776: tmp = my_localtime_rz(tz, &t, &tm);
1.17 mlelstv 777: dumptime(tmp);
778: if (tmp != NULL) {
779: if (*abbr(tmp) != '\0')
780: (void) printf(" %s", abbr(tmp));
781: if (v) {
1.44 christos 782: long off = gmtoff(tmp, NULL, gmtmp);
1.17 mlelstv 783: (void) printf(" isdst=%d", tmp->tm_isdst);
1.36 christos 784: if (off != LONG_MIN)
785: (void) printf(" gmtoff=%ld", off);
1.17 mlelstv 786: }
1.1 jtc 787: }
788: (void) printf("\n");
1.17 mlelstv 789: if (tmp != NULL && *abbr(tmp) != '\0')
790: abbrok(abbr(tmp), zone);
1.1 jtc 791: }
792:
1.44 christos 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
1.46 ! christos 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.
1.44 christos 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;
1.46 ! christos 842: return (ss || 100 <= hh
! 843: ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
1.44 christos 844: : mm
1.46 ! christos 845: ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
1.44 christos 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:
1.46 ! christos 880: /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
1.44 christos 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: }
1.46 ! christos 961: if (s <= formatted_len)
1.44 christos 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:
1.9 mycroft 993: static const char *
1.36 christos 994: abbr(struct tm const *tmp)
1.1 jtc 995: {
1.36 christos 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
1.1 jtc 1003: }
1.17 mlelstv 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 */
1.29 christos 1014: if (sizeof (time_t) == sizeof (intmax_t))
1015: return "%"PRIdMAX;
1.17 mlelstv 1016: if (sizeof (time_t) > sizeof (long))
1017: return "%lld";
1018: if (sizeof (time_t) > sizeof (int))
1019: return "%ld";
1020: return "%d";
1021: }
1.29 christos 1022: #ifdef PRIuMAX
1023: if (sizeof (time_t) == sizeof (uintmax_t))
1024: return "%"PRIuMAX;
1025: #endif
1.17 mlelstv 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
1.26 christos 1034: dumptime(const struct tm *timeptr)
1.17 mlelstv 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: };
1.26 christos 1043: const char * wn;
1044: const char * mn;
1045: int lead;
1046: int trail;
1.17 mlelstv 1047:
1048: if (timeptr == NULL) {
1.36 christos 1049: printf("NULL");
1.17 mlelstv 1050: return;
1051: }
1052: /*
1.36 christos 1053: ** The packaged localtime_rz and gmtime_r never put out-of-range
1.17 mlelstv 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];
1.36 christos 1065: printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
1.17 mlelstv 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)
1.36 christos 1082: printf("%d", trail);
1083: else printf("%d%d", lead, ((trail < 0) ? -trail : trail));
1.17 mlelstv 1084: }
CVSweb <webmaster@jp.NetBSD.org>