2 ** This file is in the public domain, so clarified as of
3 ** 2009-05-17 by Arthur David Olson.
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.
15 #ifndef NETBSD_INSPIRED
16 # define NETBSD_INSPIRED 1
26 /* Enable tm_gmtoff and tm_zone on GNUish systems. */
28 /* Enable strtoimax on Solaris 10. */
29 #define __EXTENSIONS__ 1
31 #include "stdio.h" /* for stdout, stderr, perror */
32 #include "string.h" /* for strcpy */
33 #include "sys/types.h" /* for time_t */
34 #include "time.h" /* for struct tm */
35 #include "stdlib.h" /* for exit, malloc, atoi */
36 #include "limits.h" /* for CHAR_BIT, LLONG_MAX */
40 ** Substitutes for pre-C99 compilers.
41 ** Much of this section of code is stolen from private.h.
45 # define HAVE_STDINT_H \
46 (199901 <= __STDC_VERSION__ \
47 || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \
48 || __CYGWIN__ || INTMAX_MAX)
53 #ifndef HAVE_INTTYPES_H
54 # define HAVE_INTTYPES_H HAVE_STDINT_H
57 # include <inttypes.h>
60 #ifndef INT_FAST32_MAX
61 # if INT_MAX >> 31 == 0
62 typedef long int_fast32_t;
64 typedef int int_fast32_t;
68 /* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */
69 #if !defined LLONG_MAX && defined __LONG_LONG_MAX__
70 # define LLONG_MAX __LONG_LONG_MAX__
75 typedef long long intmax_t;
76 # define strtoimax strtoll
77 # define INTMAX_MAX LLONG_MAX
79 typedef long intmax_t;
80 # define strtoimax strtol
81 # define INTMAX_MAX LONG_MAX
86 # if INTMAX_MAX == LLONG_MAX
87 # define PRIdMAX "lld"
93 /* Infer TM_ZONE on systems where this information is known, but suppress
94 guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */
95 #if (defined __GLIBC__ \
96 || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
97 || (defined __APPLE__ && defined __MACH__))
98 # if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
99 # define TM_GMTOFF tm_gmtoff
101 # if !defined TM_ZONE && !defined NO_TM_ZONE
102 # define TM_ZONE tm_zone
106 #ifndef HAVE_LOCALTIME_R
107 # define HAVE_LOCALTIME_R 1
110 #ifndef HAVE_LOCALTIME_RZ
112 # define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
114 # define HAVE_LOCALTIME_RZ 0
119 # define HAVE_TZSET 1
122 #ifndef ZDUMP_LO_YEAR
123 #define ZDUMP_LO_YEAR (-500)
124 #endif /* !defined ZDUMP_LO_YEAR */
126 #ifndef ZDUMP_HI_YEAR
127 #define ZDUMP_HI_YEAR 2500
128 #endif /* !defined ZDUMP_HI_YEAR */
130 #ifndef MAX_STRING_LENGTH
131 #define MAX_STRING_LENGTH 1024
132 #endif /* !defined MAX_STRING_LENGTH */
134 #if __STDC_VERSION__ < 199901
139 # include <stdbool.h>
143 #define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
144 #endif /* !defined TYPE_BIT */
147 #define TYPE_SIGNED(type) (((type) -1) < 0)
148 #endif /* !defined TYPE_SIGNED */
150 #ifndef INT_STRLEN_MAXIMUM
152 ** 302 / 1000 is log10(2.0) rounded up.
153 ** Subtract one for the sign bit if the type is signed;
154 ** add one for integer division truncation;
155 ** add one more for a minus sign if the type is signed.
157 #define INT_STRLEN_MAXIMUM(type) \
158 ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
159 1 + TYPE_SIGNED(type))
160 #endif /* !defined INT_STRLEN_MAXIMUM */
163 #define EXIT_SUCCESS 0
164 #endif /* !defined EXIT_SUCCESS */
167 #define EXIT_FAILURE 1
168 #endif /* !defined EXIT_FAILURE */
171 #define SECSPERMIN 60
172 #endif /* !defined SECSPERMIN */
175 #define MINSPERHOUR 60
176 #endif /* !defined MINSPERHOUR */
179 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
180 #endif /* !defined SECSPERHOUR */
183 #define HOURSPERDAY 24
184 #endif /* !defined HOURSPERDAY */
187 #define EPOCH_YEAR 1970
188 #endif /* !defined EPOCH_YEAR */
191 #define TM_YEAR_BASE 1900
192 #endif /* !defined TM_YEAR_BASE */
195 #define DAYSPERNYEAR 365
196 #endif /* !defined DAYSPERNYEAR */
199 #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
200 #endif /* !defined isleap */
204 ** See tzfile.h for details on isleap_sum.
206 #define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
207 #endif /* !defined isleap_sum */
209 #define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
210 #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
211 #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
212 #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
213 + SECSPERLYEAR * (intmax_t) (100 - 3))
216 ** True if SECSPER400YEARS is known to be representable as an
217 ** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
218 ** even if SECSPER400YEARS is representable, because when that happens
219 ** the code merely runs a bit more slowly, and this slowness doesn't
220 ** occur on any practical platform.
222 enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
225 #define HAVE_GETTEXT 0
228 #include "locale.h" /* for setlocale */
230 #endif /* HAVE_GETTEXT */
232 #if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)
233 # define ATTRIBUTE_PURE __attribute__ ((__pure__))
235 # define ATTRIBUTE_PURE /* empty */
239 ** For the benefit of GNU folk...
240 ** '_(MSGID)' uses the current locale's message library string for MSGID.
241 ** The default is to use gettext if available, and use MSGID otherwise.
246 #define _(msgid) gettext(msgid)
247 #else /* !HAVE_GETTEXT */
248 #define _(msgid) msgid
249 #endif /* !HAVE_GETTEXT */
250 #endif /* !defined _ */
252 #if !defined TZ_DOMAIN && defined HAVE_GETTEXT
253 # define TZ_DOMAIN "tz"
256 #if ! HAVE_LOCALTIME_RZ
258 # define timezone_t char **
261 extern char ** environ;
263 #if !HAVE_POSIX_DECLS
264 extern int getopt(int argc, char * const argv[],
265 const char * options);
266 extern char * optarg;
268 extern char * tzname[];
271 /* The minimum and maximum finite time values. */
272 enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 };
273 static time_t const absolute_min_time =
275 ? (- ((time_t) ~ (time_t) 0 < 0)
276 - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
278 static time_t const absolute_max_time =
280 ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
283 static char * progname;
287 static char const *abbr(struct tm const *);
288 static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE;
289 static void dumptime(struct tm const *);
290 static time_t hunt(timezone_t, char *, time_t, time_t);
291 static void show(timezone_t, char *, time_t, bool);
292 static void showtrans(char const *, struct tm const *, time_t, char const *,
294 static const char *tformat(void);
295 static time_t yeartot(intmax_t) ATTRIBUTE_PURE;
297 /* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
298 #define is_digit(c) ((unsigned)(c) - '0' <= 9)
300 /* Is A an alphabetic character in the C locale? */
307 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
308 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
309 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
310 case 'V': case 'W': case 'X': case 'Y': case 'Z':
311 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
312 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
313 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
314 case 'v': case 'w': case 'x': case 'y': case 'z':
319 /* Return A + B, exiting if the result would overflow. */
321 sumsize(size_t a, size_t b)
325 fprintf(stderr, "%s: size overflow\n", progname);
331 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
332 on failure. SIZE should be nonzero. */
336 void *p = malloc(size);
346 # define tzset zdump_tzset
347 static void tzset(void) { }
350 /* Assume gmtime_r works if localtime_r does.
351 A replacement localtime_r is defined below if needed. */
352 #if ! HAVE_LOCALTIME_R
355 # define gmtime_r zdump_gmtime_r
358 gmtime_r(time_t *tp, struct tm *tmp)
360 struct tm *r = gmtime(tp);
370 /* Platforms with TM_ZONE don't need tzname, so they can use the
371 faster localtime_rz or localtime_r if available. */
373 #if defined TM_ZONE && HAVE_LOCALTIME_RZ
374 # define USE_LOCALTIME_RZ true
376 # define USE_LOCALTIME_RZ false
379 #if ! USE_LOCALTIME_RZ
381 # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
383 # define localtime_r zdump_localtime_r
385 localtime_r(time_t *tp, struct tm *tmp)
387 struct tm *r = localtime(tp);
397 # define localtime_rz zdump_localtime_rz
399 localtime_rz(timezone_t rz, time_t *tp, struct tm *tmp)
401 return localtime_r(tp, tmp);
406 # define mktime_z zdump_mktime_z
408 mktime_z(timezone_t tz, struct tm *tmp)
416 # define tzalloc zdump_tzalloc
417 # define tzfree zdump_tzfree
420 tzalloc(char const *val)
422 static char **fakeenv;
423 char **env = fakeenv;
431 env = xmalloc(sumsize(sizeof *environ,
432 (e - environ) * sizeof *environ));
434 for (e = environ; (env[to] = *e); e++)
435 to += strncmp(*e, "TZ=", 3) != 0;
437 env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val)));
438 env[0] = strcat(strcpy(env0, "TZ="), val);
439 environ = fakeenv = env;
445 tzfree(timezone_t env)
450 #endif /* ! USE_LOCALTIME_RZ */
452 /* A UTC time zone, and its initializer. */
453 static timezone_t gmtz;
457 if (USE_LOCALTIME_RZ) {
458 static char const utc[] = "UTC0";
467 /* Convert *TP to UTC, storing the broken-down time into *TMP.
468 Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
469 except typically faster if USE_LOCALTIME_RZ. */
471 my_gmtime_r(time_t *tp, struct tm *tmp)
473 return USE_LOCALTIME_RZ ? localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
477 # define my_localtime_rz localtime_rz
478 #else /* !defined TYPECHECK */
481 my_localtime_rz(timezone_t tz, time_t *tp, struct tm *tmp)
483 tmp = localtime_rz(tz, tp, tmp);
489 t = mktime_z(tz, &tm);
492 fprintf(stderr, "\n%s: ", progname);
493 fprintf(stderr, tformat(), *tp);
494 fprintf(stderr, " ->");
495 fprintf(stderr, " year=%d", tmp->tm_year);
496 fprintf(stderr, " mon=%d", tmp->tm_mon);
497 fprintf(stderr, " mday=%d", tmp->tm_mday);
498 fprintf(stderr, " hour=%d", tmp->tm_hour);
499 fprintf(stderr, " min=%d", tmp->tm_min);
500 fprintf(stderr, " sec=%d", tmp->tm_sec);
501 fprintf(stderr, " isdst=%d", tmp->tm_isdst);
502 fprintf(stderr, " -> ");
503 fprintf(stderr, tformat(), t);
504 fprintf(stderr, "\n");
510 #endif /* !defined TYPECHECK */
513 abbrok(const char *const abbrp, const char *const zone)
515 register const char * cp;
516 register const char * wp;
521 while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
524 wp = _("has fewer than 3 characters");
525 else if (cp - abbrp > 6)
526 wp = _("has more than 6 characters");
528 wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
533 _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
534 progname, zone, abbrp, wp);
535 warned = errout = true;
538 /* Return a time zone abbreviation. If the abbreviation needs to be
539 saved, use *BUF (of size *BUFALLOC) to save it, and return the
540 abbreviation in the possibly-reallocated *BUF. Otherwise, just
541 return the abbreviation. Get the abbreviation from TMP.
542 Exit on memory allocation failure. */
544 saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp)
546 char const *ab = abbr(tmp);
547 if (HAVE_LOCALTIME_RZ)
550 size_t ablen = strlen(ab);
551 if (*bufalloc <= ablen) {
554 /* Make the new buffer at least twice as long as the old,
555 to avoid O(N**2) behavior on repeated calls. */
556 *bufalloc = sumsize(*bufalloc, ablen + 1);
558 *buf = xmalloc(*bufalloc);
560 return strcpy(*buf, ab);
565 close_file(FILE *stream)
567 char const *e = (ferror(stream) ? _("I/O error")
568 : fclose(stream) != 0 ? strerror(errno) : NULL);
570 fprintf(stderr, "%s: %s\n", progname, e);
576 usage(FILE * const stream, const int status)
579 _("%s: usage: %s OPTIONS ZONENAME ...\n"
581 " -c [L,]U Start at year L (default -500), end before year U (default 2500)\n"
582 " -t [L,]U Start at time L, end before time U (in seconds since 1970)\n"
583 " -i List transitions briefly (format is experimental)\n" \
584 " -v List transitions verbosely\n"
585 " -V List transitions a bit less verbosely\n"
586 " --help Output this help\n"
587 " --version Output version info\n"
589 "Report bugs to %s.\n"),
590 progname, progname, REPORT_BUGS_TO);
591 if (status == EXIT_SUCCESS)
597 main(int argc, char *argv[])
599 /* These are static so that they're initially zero. */
600 static char * abbrev;
601 static size_t abbrevsize;
606 register char * cutarg;
607 register char * cuttimes;
608 register time_t cutlotime;
609 register time_t cuthitime;
613 cutlotime = absolute_min_time;
614 cuthitime = absolute_max_time;
616 setlocale(LC_ALL, "");
618 bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
619 #endif /* defined TEXTDOMAINDIR */
620 textdomain(TZ_DOMAIN);
621 #endif /* HAVE_GETTEXT */
623 for (i = 1; i < argc; ++i)
624 if (strcmp(argv[i], "--version") == 0) {
625 printf("zdump %s%s\n", PKGVERSION, TZVERSION);
627 } else if (strcmp(argv[i], "--help") == 0) {
628 usage(stdout, EXIT_SUCCESS);
630 vflag = Vflag = false;
631 cutarg = cuttimes = NULL;
633 switch (getopt(argc, argv, "c:it:vV")) {
634 case 'c': cutarg = optarg; break;
635 case 't': cuttimes = optarg; break;
636 case 'i': iflag = true; break;
637 case 'v': vflag = true; break;
638 case 'V': Vflag = true; break;
640 if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
641 goto arg_processing_done;
644 usage(stderr, EXIT_FAILURE);
646 arg_processing_done:;
648 if (iflag | vflag | Vflag) {
652 register intmax_t cutloyear = ZDUMP_LO_YEAR;
653 register intmax_t cuthiyear = ZDUMP_HI_YEAR;
654 if (cutarg != NULL) {
655 lo = strtoimax(cutarg, &loend, 10);
656 if (cutarg != loend && !*loend) {
659 } else if (cutarg != loend && *loend == ','
660 && (hi = strtoimax(loend + 1, &hiend, 10),
661 loend + 1 != hiend && !*hiend)) {
665 fprintf(stderr, _("%s: wild -c argument %s\n"),
670 if (cutarg != NULL || cuttimes == NULL) {
671 cutlotime = yeartot(cutloyear);
672 cuthitime = yeartot(cuthiyear);
674 if (cuttimes != NULL) {
675 lo = strtoimax(cuttimes, &loend, 10);
676 if (cuttimes != loend && !*loend) {
678 if (hi < cuthitime) {
679 if (hi < absolute_min_time)
680 hi = absolute_min_time;
683 } else if (cuttimes != loend && *loend == ','
684 && (hi = strtoimax(loend + 1, &hiend, 10),
685 loend + 1 != hiend && !*hiend)) {
686 if (cutlotime < lo) {
687 if (absolute_max_time < lo)
688 lo = absolute_max_time;
691 if (hi < cuthitime) {
692 if (hi < absolute_min_time)
693 hi = absolute_min_time;
698 _("%s: wild -t argument %s\n"),
706 if (! (iflag | vflag | Vflag))
709 for (i = optind; i < argc; i++) {
710 size_t arglen = strlen(argv[i]);
711 if (longest < arglen)
712 longest = arglen < INT_MAX ? arglen : INT_MAX;
715 for (i = optind; i < argc; ++i) {
716 timezone_t tz = tzalloc(argv[i]);
725 if (! (iflag | vflag | Vflag)) {
726 show(tz, argv[i], now, false);
731 t = absolute_min_time;
732 if (! (iflag | Vflag)) {
733 show(tz, argv[i], t, true);
735 show(tz, argv[i], t, true);
739 tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
741 ab = saveabbr(&abbrev, &abbrevsize, &tm);
743 showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
744 showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
747 while (t < cuthitime) {
748 time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
749 && t + SECSPERDAY / 2 < cuthitime)
752 struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
753 bool newtm_ok = newtmp != NULL;
754 if (! (tm_ok & newtm_ok
755 ? (delta(&newtm, &tm) == newt - t
756 && newtm.tm_isdst == tm.tm_isdst
757 && strcmp(abbr(&newtm), ab) == 0)
758 : tm_ok == newtm_ok)) {
759 newt = hunt(tz, argv[i], t, newt);
760 newtmp = localtime_rz(tz, &newt, &newtm);
761 newtm_ok = newtmp != NULL;
763 showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt,
764 newtm_ok ? abbr(&newtm) : NULL, argv[i]);
766 show(tz, argv[i], newt - 1, true);
767 show(tz, argv[i], newt, true);
773 ab = saveabbr(&abbrev, &abbrevsize, &newtm);
777 if (! (iflag | Vflag)) {
778 t = absolute_max_time;
780 show(tz, argv[i], t, true);
782 show(tz, argv[i], t, true);
787 if (errout && (ferror(stderr) || fclose(stderr) != 0))
795 register intmax_t myy, seconds, years;
801 if (SECSPER400YEARS_FITS && 400 <= y - myy) {
802 intmax_t diff400 = (y - myy) / 400;
803 if (INTMAX_MAX / SECSPER400YEARS < diff400)
804 return absolute_max_time;
805 seconds = diff400 * SECSPER400YEARS;
806 years = diff400 * 400;
808 seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
812 if (t > absolute_max_time - seconds)
813 return absolute_max_time;
817 if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
818 intmax_t diff400 = (myy - y) / 400;
819 if (INTMAX_MAX / SECSPER400YEARS < diff400)
820 return absolute_min_time;
821 seconds = diff400 * SECSPER400YEARS;
822 years = diff400 * 400;
824 seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
828 if (t < absolute_min_time + seconds)
829 return absolute_min_time;
836 hunt(timezone_t tz, char *name, time_t lot, time_t hit)
839 static size_t loabsize;
844 bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
848 ab = saveabbr(&loab, &loabsize, &lotm);
850 time_t diff = hit - lot;
859 tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
861 ? (delta(&tm, &lotm) == t - lot
862 && tm.tm_isdst == lotm.tm_isdst
863 && strcmp(abbr(&tm), ab) == 0)
864 : lotm_ok == tm_ok) {
874 ** Thanks to Paul Eggert for logic used in delta.
878 delta(struct tm * newp, struct tm *oldp)
880 register intmax_t result;
883 if (newp->tm_year < oldp->tm_year)
884 return -delta(oldp, newp);
886 for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
887 result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
888 result += newp->tm_yday - oldp->tm_yday;
889 result *= HOURSPERDAY;
890 result += newp->tm_hour - oldp->tm_hour;
891 result *= MINSPERHOUR;
892 result += newp->tm_min - oldp->tm_min;
893 result *= SECSPERMIN;
894 result += newp->tm_sec - oldp->tm_sec;
899 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
900 Assume A and B differ by at most one year. */
902 adjusted_yday(struct tm const *a, struct tm const *b)
904 int yday = a->tm_yday;
905 if (b->tm_year < a->tm_year)
906 yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
911 /* If A is the broken-down local time and B the broken-down UTC for
912 the same instant, return A's UTC offset in seconds, where positive
913 offsets are east of Greenwich. On failure, return LONG_MIN.
915 If T is nonnull, *T is the time stamp that corresponds to A; call
916 my_gmtime_r and use its result instead of B. Otherwise, B is the
917 possibly nonnull result of an earlier call to my_gmtime_r. */
919 gmtoff(struct tm const *a, time_t *t, struct tm const *b)
926 b = my_gmtime_r(t, &tm);
930 int ayday = adjusted_yday(a, b);
931 int byday = adjusted_yday(b, a);
932 int days = ayday - byday;
933 long hours = a->tm_hour - b->tm_hour + 24 * days;
934 long minutes = a->tm_min - b->tm_min + 60 * hours;
935 long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
942 show(timezone_t tz, char *zone, time_t t, bool v)
944 register struct tm * tmp;
945 register struct tm * gmtmp;
948 printf("%-*s ", longest, zone);
950 gmtmp = my_gmtime_r(&t, &gmtm);
952 printf(tformat(), t);
959 tmp = my_localtime_rz(tz, &t, &tm);
962 if (*abbr(tmp) != '\0')
963 printf(" %s", abbr(tmp));
965 long off = gmtoff(tmp, NULL, gmtmp);
966 printf(" isdst=%d", tmp->tm_isdst);
968 printf(" gmtoff=%ld", off);
972 if (tmp != NULL && *abbr(tmp) != '\0')
973 abbrok(abbr(tmp), zone);
976 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
977 Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit
978 :MM too if MM is also zero.
980 Return the length of the resulting string. If the string does not
981 fit, return the length that the string would have been if it had
982 fit; do not overrun the output buffer. */
984 format_local_time(char *buf, size_t size, struct tm const *tm)
986 int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
988 ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
990 ? snprintf(buf, size, "%02d:%02d", hh, mm)
991 : snprintf(buf, size, "%02d", hh));
994 /* Store into BUF, of size SIZE, a formatted UTC offset for the
995 localtime *TM corresponding to time T. Use ISO 8601 format
996 +HH:MM:SS, or -HH:MM:SS for time stamps west of Greenwich. Omit
997 :SS if :SS is zero, and omit :MM too if :MM is also zero. If the
998 time stamp represents an unknown UTC offset, use the format -00.
1000 Return the length of the resulting string, or -1 if the result is
1001 not representable as a string. If the string does not fit, return
1002 the length that the string would have been if it had fit; do not
1003 overrun the output buffer. */
1005 format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
1007 long off = gmtoff(tm, &t, NULL);
1008 char sign = ((off < 0
1010 && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
1016 if (off == LONG_MIN)
1024 ? snprintf(buf, size, "%c%02ld:%02d:%02d", sign, hh, mm, ss)
1026 ? snprintf(buf, size, "%c%02ld:%02d", sign, hh, mm)
1027 : snprintf(buf, size, "%c%02ld", sign, hh));
1030 /* Store into BUF (of size SIZE) a quoted string representation of P.
1031 If the representation's length is less than SIZE, return the
1032 length; the representation is not null terminated. Otherwise
1033 return SIZE, to indicate that BUF is too small. */
1035 format_quoted_string(char *buf, size_t size, char const *p)
1047 default: *b++ = c, s--; continue;
1048 case '\0': *b++ = '"', s--; return size - s;
1049 case '"': case '\\': break;
1050 case ' ': c = 's'; break;
1051 case '\f': c = 'f'; break;
1052 case '\n': c = 'n'; break;
1053 case '\r': c = 'r'; break;
1054 case '\t': c = 't'; break;
1055 case '\v': c = 'v'; break;
1057 *b++ = '\\', *b++ = c, s -= 2;
1061 /* Store into BUF (of size SIZE) a time stamp formatted by TIME_FMT.
1062 TM is the broken-down time, T the seconds count, AB the time zone
1063 abbreviation, and ZONE_NAME the zone name. Return true if
1064 successful, false if the output would require more than SIZE bytes.
1065 TIME_FMT uses the same format that strftime uses, with these
1069 %L local time as per format_local_time
1070 %Q like "U\t%Z\tD" where U is the UTC offset as for format_utc_offset
1071 and D is the isdst flag; except omit D if it is zero, omit %Z if
1072 it equals U, quote and escape %Z if it contains nonalphabetics,
1073 and omit any trailing tabs. */
1076 istrftime(char *buf, size_t size, char const *time_fmt,
1077 struct tm const *tm, time_t t, char const *ab, char const *zone_name)
1081 char const *f = time_fmt, *p;
1084 if (*p == '%' && p[1] == '%')
1088 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
1089 size_t formatted_len;
1090 size_t f_prefix_len = p - f;
1091 size_t f_prefix_copy_size = p - f + 2;
1093 bool oversized = sizeof fbuf <= f_prefix_copy_size;
1094 char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
1095 memcpy(f_prefix_copy, f, f_prefix_len);
1096 strcpy(f_prefix_copy + f_prefix_len, "X");
1097 formatted_len = strftime(b, s, f_prefix_copy, tm);
1099 free(f_prefix_copy);
1100 if (formatted_len == 0)
1103 b += formatted_len, s -= formatted_len;
1108 formatted_len = format_quoted_string(b, s, zone_name);
1111 formatted_len = format_local_time(b, s, tm);
1116 int offlen = format_utc_offset(b, s, tm, t);
1117 if (! (0 <= offlen && offlen < s))
1119 show_abbr = strcmp(b, ab) != 0;
1120 b += offlen, s -= offlen;
1127 for (abp = ab; is_alpha(*abp); abp++)
1130 ? snprintf(b, s, "%s", ab)
1131 : format_quoted_string(b, s, ab));
1136 formatted_len = (tm->tm_isdst
1137 ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
1142 if (! (0 <= formatted_len && formatted_len < s))
1144 b += formatted_len, s -= formatted_len;
1151 /* Show a time transition. */
1153 showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
1154 char const *zone_name)
1157 printf(tformat(), t);
1160 char stackbuf[1000];
1161 size_t size = sizeof stackbuf;
1162 char *buf = stackbuf;
1163 char *bufalloc = NULL;
1164 while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
1165 size = sumsize(size, size);
1167 buf = bufalloc = xmalloc(size);
1175 abbr(struct tm const *tmp)
1178 return tmp->TM_ZONE;
1180 return (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst]
1181 ? tzname[0 < tmp->tm_isdst]
1187 ** The code below can fail on certain theoretical systems;
1188 ** it works on all known real-world systems as of 2004-12-30.
1194 if (0 > (time_t) -1) { /* signed */
1195 if (sizeof (time_t) == sizeof (intmax_t))
1197 if (sizeof (time_t) > sizeof (long))
1199 if (sizeof (time_t) > sizeof (int))
1204 if (sizeof (time_t) == sizeof (uintmax_t))
1207 if (sizeof (time_t) > sizeof (unsigned long))
1209 if (sizeof (time_t) > sizeof (unsigned int))
1215 dumptime(register const struct tm *timeptr)
1217 static const char wday_name[][3] = {
1218 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1220 static const char mon_name[][3] = {
1221 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1222 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1224 register const char * wn;
1225 register const char * mn;
1229 if (timeptr == NULL) {
1234 ** The packaged localtime_rz and gmtime_r never put out-of-range
1235 ** values in tm_wday or tm_mon, but since this code might be compiled
1236 ** with other (perhaps experimental) versions, paranoia is in order.
1238 if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
1239 (int) (sizeof wday_name / sizeof wday_name[0]))
1241 else wn = wday_name[timeptr->tm_wday];
1242 if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
1243 (int) (sizeof mon_name / sizeof mon_name[0]))
1245 else mn = mon_name[timeptr->tm_mon];
1246 printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
1248 timeptr->tm_mday, timeptr->tm_hour,
1249 timeptr->tm_min, timeptr->tm_sec);
1251 trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
1252 lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
1255 if (trail < 0 && lead > 0) {
1258 } else if (lead < 0 && trail > 0) {
1263 printf("%d", trail);
1264 else printf("%d%d", lead, ((trail < 0) ? -trail : trail));