1 /* Dump time zone data in a textual format. */
4 ** This file is in the public domain, so clarified as of
5 ** 2009-05-17 by Arthur David Olson.
10 #ifndef NETBSD_INSPIRED
11 # define NETBSD_INSPIRED 1
18 # define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
21 #ifndef HAVE_LOCALTIME_R
22 # define HAVE_LOCALTIME_R 1
25 #ifndef HAVE_LOCALTIME_RZ
27 # define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
29 # define HAVE_LOCALTIME_RZ 0
38 # define ZDUMP_LO_YEAR (-500)
39 #endif /* !defined ZDUMP_LO_YEAR */
42 # define ZDUMP_HI_YEAR 2500
43 #endif /* !defined ZDUMP_HI_YEAR */
45 #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
46 #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
47 #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
48 + SECSPERLYEAR * (intmax_t) (100 - 3))
51 ** True if SECSPER400YEARS is known to be representable as an
52 ** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
53 ** even if SECSPER400YEARS is representable, because when that happens
54 ** the code merely runs a bit more slowly, and this slowness doesn't
55 ** occur on any practical platform.
57 enum { SECSPER400YEARS_FITS
= SECSPERLYEAR
<= INTMAX_MAX
/ 400 };
60 # include <locale.h> /* for setlocale */
61 #endif /* HAVE_GETTEXT */
63 #if ! HAVE_LOCALTIME_RZ
65 # define timezone_t char **
69 extern int getopt(int argc
, char * const argv
[],
70 const char * options
);
75 /* The minimum and maximum finite time values. */
76 enum { atime_shift
= CHAR_BIT
* sizeof(time_t) - 2 };
77 static time_t const absolute_min_time
=
79 ? (- ((time_t) ~ (time_t) 0 < 0)
80 - (((time_t) 1 << atime_shift
) - 1 + ((time_t) 1 << atime_shift
)))
82 static time_t const absolute_max_time
=
84 ? (((time_t) 1 << atime_shift
) - 1 + ((time_t) 1 << atime_shift
))
87 static char const *progname
;
91 static char const *abbr(struct tm
const *);
92 ATTRIBUTE_REPRODUCIBLE
static intmax_t delta(struct tm
*, struct tm
*);
93 static void dumptime(struct tm
const *);
94 static time_t hunt(timezone_t
, time_t, time_t, bool);
95 static void show(timezone_t
, char *, time_t, bool);
96 static void showextrema(timezone_t
, char *, time_t, struct tm
*, time_t);
97 static void showtrans(char const *, struct tm
const *, time_t, char const *,
99 static const char *tformat(void);
100 ATTRIBUTE_REPRODUCIBLE
static time_t yeartot(intmax_t);
102 /* Is C an ASCII digit? */
106 return '0' <= c
&& c
<= '9';
109 /* Is A an alphabetic character in the C locale? */
116 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
117 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
118 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
119 case 'V': case 'W': case 'X': case 'Y': case 'Z':
120 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
121 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
122 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
123 case 'v': case 'w': case 'x': case 'y': case 'z':
128 ATTRIBUTE_NORETURN
static void
131 fprintf(stderr
, _("%s: size overflow\n"), progname
);
135 /* Return A + B, exiting if the result would overflow either ptrdiff_t
136 or size_t. A and B are both nonnegative. */
137 ATTRIBUTE_REPRODUCIBLE
static ptrdiff_t
138 sumsize(ptrdiff_t a
, ptrdiff_t b
)
142 if (!ckd_add(&sum
, a
, b
) && sum
<= INDEX_MAX
)
145 if (a
<= INDEX_MAX
&& b
<= INDEX_MAX
- a
)
151 /* Return the size of of the string STR, including its trailing NUL.
152 Report an error and exit if this would exceed INDEX_MAX which means
153 pointer subtraction wouldn't work. */
155 xstrsize(char const *str
)
157 size_t len
= strlen(str
);
163 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
164 on failure. SIZE should be positive. */
165 ATTRIBUTE_MALLOC
static void *
166 xmalloc(ptrdiff_t size
)
168 void *p
= malloc(size
);
170 fprintf(stderr
, _("%s: Memory exhausted\n"), progname
);
178 # define tzset zdump_tzset
179 static void tzset(void) { }
182 /* Assume gmtime_r works if localtime_r does.
183 A replacement localtime_r is defined below if needed. */
184 #if ! HAVE_LOCALTIME_R
187 # define gmtime_r zdump_gmtime_r
190 gmtime_r(time_t *tp
, struct tm
*tmp
)
192 struct tm
*r
= gmtime(tp
);
202 /* Platforms with TM_ZONE don't need tzname, so they can use the
203 faster localtime_rz or localtime_r if available. */
205 #if defined TM_ZONE && HAVE_LOCALTIME_RZ
206 # define USE_LOCALTIME_RZ true
208 # define USE_LOCALTIME_RZ false
211 #if ! USE_LOCALTIME_RZ
213 # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
215 # define localtime_r zdump_localtime_r
217 localtime_r(time_t *tp
, struct tm
*tmp
)
219 struct tm
*r
= localtime(tp
);
229 # define localtime_rz zdump_localtime_rz
231 localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz
, time_t *tp
, struct tm
*tmp
)
233 return localtime_r(tp
, tmp
);
238 # define mktime_z zdump_mktime_z
240 mktime_z(timezone_t tz
, struct tm
*tmp
)
248 # define tzalloc zdump_tzalloc
249 # define tzfree zdump_tzfree
252 tzalloc(char const *val
)
255 if (setenv("TZ", val
, 1) != 0) {
256 char const *e
= strerror(errno
);
257 fprintf(stderr
, _("%s: setenv: %s\n"), progname
, e
);
261 return &optarg
; /* Any valid non-null char ** will do. */
263 enum { TZeqlen
= 3 };
264 static char const TZeq
[TZeqlen
] = "TZ=";
265 static char **fakeenv
;
266 static ptrdiff_t fakeenv0size
;
267 void *freeable
= NULL
;
268 char **env
= fakeenv
, **initial_environ
;
269 ptrdiff_t valsize
= xstrsize(val
);
270 if (fakeenv0size
< valsize
) {
271 char **e
= environ
, **to
;
272 ptrdiff_t initial_nenvptrs
= 1; /* Counting the trailing NULL pointer. */
276 if (ckd_add(&initial_nenvptrs
, initial_nenvptrs
, 1)
277 || INDEX_MAX
< initial_nenvptrs
)
280 if (initial_nenvptrs
== INDEX_MAX
/ sizeof *environ
)
285 fakeenv0size
= sumsize(valsize
, valsize
);
286 fakeenv0size
= max(fakeenv0size
, 64);
289 xmalloc(sumsize(sumsize(sizeof *environ
,
290 initial_nenvptrs
* sizeof *environ
),
291 sumsize(TZeqlen
, fakeenv0size
)));
293 for (e
= environ
; (*to
= *e
); e
++)
294 to
+= strncmp(*e
, TZeq
, TZeqlen
) != 0;
295 env
[0] = memcpy(to
+ 1, TZeq
, TZeqlen
);
297 memcpy(env
[0] + TZeqlen
, val
, valsize
);
298 initial_environ
= environ
;
302 return initial_environ
;
307 tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ
)
310 environ
= initial_environ
;
314 #endif /* ! USE_LOCALTIME_RZ */
316 /* A UT time zone, and its initializer. */
317 static timezone_t gmtz
;
321 if (USE_LOCALTIME_RZ
) {
322 /* Try "GMT" first to find out whether this is one of the rare
323 platforms where time_t counts leap seconds; this works due to
324 the "Zone GMT 0 - GMT" line in the "etcetera" file. If "GMT"
325 fails, fall back on "GMT0" which might be similar due to the
326 "Link GMT GMT0" line in the "backward" file, and which
327 should work on all POSIX platforms. The rest of zdump does not
328 use the "GMT" abbreviation that comes from this setting, so it
329 is OK to use "GMT" here rather than the modern "UTC" which
330 would not work on platforms that omit the "backward" file. */
331 gmtz
= tzalloc("GMT");
333 static char const gmt0
[] = "GMT0";
334 gmtz
= tzalloc(gmt0
);
336 char const *e
= strerror(errno
);
337 fprintf(stderr
, _("%s: unknown timezone '%s': %s\n"),
345 /* Convert *TP to UT, storing the broken-down time into *TMP.
346 Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
347 except typically faster if USE_LOCALTIME_RZ. */
349 my_gmtime_r(time_t *tp
, struct tm
*tmp
)
351 return USE_LOCALTIME_RZ
? localtime_rz(gmtz
, tp
, tmp
) : gmtime_r(tp
, tmp
);
355 # define my_localtime_rz localtime_rz
356 #else /* !defined TYPECHECK */
359 my_localtime_rz(timezone_t tz
, time_t *tp
, struct tm
*tmp
)
361 tmp
= localtime_rz(tz
, tp
, tmp
);
367 t
= mktime_z(tz
, &tm
);
370 fprintf(stderr
, "\n%s: ", progname
);
371 fprintf(stderr
, tformat(), *tp
);
372 fprintf(stderr
, " ->");
373 fprintf(stderr
, " year=%d", tmp
->tm_year
);
374 fprintf(stderr
, " mon=%d", tmp
->tm_mon
);
375 fprintf(stderr
, " mday=%d", tmp
->tm_mday
);
376 fprintf(stderr
, " hour=%d", tmp
->tm_hour
);
377 fprintf(stderr
, " min=%d", tmp
->tm_min
);
378 fprintf(stderr
, " sec=%d", tmp
->tm_sec
);
379 fprintf(stderr
, " isdst=%d", tmp
->tm_isdst
);
380 fprintf(stderr
, " -> ");
381 fprintf(stderr
, tformat(), t
);
382 fprintf(stderr
, "\n");
388 #endif /* !defined TYPECHECK */
391 abbrok(const char *const abbrp
, const char *const zone
)
393 register const char * cp
;
394 register const char * wp
;
399 while (is_alpha(*cp
) || is_digit(*cp
) || *cp
== '-' || *cp
== '+')
402 wp
= _("has characters other than ASCII alphanumerics, '-' or '+'");
403 else if (cp
- abbrp
< 3)
404 wp
= _("has fewer than 3 characters");
405 else if (cp
- abbrp
> 6)
406 wp
= _("has more than 6 characters");
411 _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
412 progname
, zone
, abbrp
, wp
);
413 warned
= errout
= true;
416 /* Return a time zone abbreviation. If the abbreviation needs to be
417 saved, use *BUF (of size *BUFALLOC) to save it, and return the
418 abbreviation in the possibly reallocated *BUF. Otherwise, just
419 return the abbreviation. Get the abbreviation from TMP.
420 Exit on memory allocation failure. */
422 saveabbr(char **buf
, ptrdiff_t *bufalloc
, struct tm
const *tmp
)
424 char const *ab
= abbr(tmp
);
425 if (HAVE_LOCALTIME_RZ
)
428 ptrdiff_t absize
= xstrsize(ab
);
429 if (*bufalloc
< absize
) {
432 /* Make the new buffer at least twice as long as the old,
433 to avoid O(N**2) behavior on repeated calls. */
434 *bufalloc
= sumsize(*bufalloc
, absize
);
436 *buf
= xmalloc(*bufalloc
);
438 return strcpy(*buf
, ab
);
443 close_file(FILE *stream
)
445 char const *e
= (ferror(stream
) ? _("I/O error")
446 : fclose(stream
) != 0 ? strerror(errno
) : NULL
);
448 fprintf(stderr
, "%s: %s\n", progname
, e
);
454 usage(FILE * const stream
, const int status
)
457 _("%s: usage: %s OPTIONS TIMEZONE ...\n"
459 " -c [L,]U Start at year L (default -500), end before year U (default 2500)\n"
460 " -t [L,]U Start at time L, end before time U (in seconds since 1970)\n"
461 " -i List transitions briefly (format is experimental)\n" \
462 " -v List transitions verbosely\n"
463 " -V List transitions a bit less verbosely\n"
464 " --help Output this help\n"
465 " --version Output version info\n"
467 "Report bugs to %s.\n"),
468 progname
, progname
, REPORT_BUGS_TO
);
469 if (status
== EXIT_SUCCESS
)
475 main(int argc
, char *argv
[])
477 /* These are static so that they're initially zero. */
478 static char * abbrev
;
479 static ptrdiff_t abbrevsize
;
484 register char * cutarg
;
485 register char * cuttimes
;
486 register time_t cutlotime
;
487 register time_t cuthitime
;
491 cutlotime
= absolute_min_time
;
492 cuthitime
= absolute_max_time
;
494 setlocale(LC_ALL
, "");
496 bindtextdomain(TZ_DOMAIN
, TZ_DOMAINDIR
);
497 # endif /* defined TEXTDOMAINDIR */
498 textdomain(TZ_DOMAIN
);
499 #endif /* HAVE_GETTEXT */
500 progname
= argv
[0] ? argv
[0] : "zdump";
501 for (i
= 1; i
< argc
; ++i
)
502 if (strcmp(argv
[i
], "--version") == 0) {
503 printf("zdump %s%s\n", PKGVERSION
, TZVERSION
);
505 } else if (strcmp(argv
[i
], "--help") == 0) {
506 usage(stdout
, EXIT_SUCCESS
);
508 vflag
= Vflag
= false;
509 cutarg
= cuttimes
= NULL
;
511 switch (getopt(argc
, argv
, "c:it:vV")) {
512 case 'c': cutarg
= optarg
; break;
513 case 't': cuttimes
= optarg
; break;
514 case 'i': iflag
= true; break;
515 case 'v': vflag
= true; break;
516 case 'V': Vflag
= true; break;
518 if (! (optind
== argc
- 1 && strcmp(argv
[optind
], "=") == 0))
519 goto arg_processing_done
;
520 ATTRIBUTE_FALLTHROUGH
;
522 usage(stderr
, EXIT_FAILURE
);
524 arg_processing_done
:;
526 if (iflag
| vflag
| Vflag
) {
530 register intmax_t cutloyear
= ZDUMP_LO_YEAR
;
531 register intmax_t cuthiyear
= ZDUMP_HI_YEAR
;
532 if (cutarg
!= NULL
) {
533 lo
= strtoimax(cutarg
, &loend
, 10);
534 if (cutarg
!= loend
&& !*loend
) {
537 } else if (cutarg
!= loend
&& *loend
== ','
538 && (hi
= strtoimax(loend
+ 1, &hiend
, 10),
539 loend
+ 1 != hiend
&& !*hiend
)) {
543 fprintf(stderr
, _("%s: wild -c argument %s\n"),
548 if (cutarg
!= NULL
|| cuttimes
== NULL
) {
549 cutlotime
= yeartot(cutloyear
);
550 cuthitime
= yeartot(cuthiyear
);
552 if (cuttimes
!= NULL
) {
553 lo
= strtoimax(cuttimes
, &loend
, 10);
554 if (cuttimes
!= loend
&& !*loend
) {
556 if (hi
< cuthitime
) {
557 if (hi
< absolute_min_time
+ 1)
558 hi
= absolute_min_time
+ 1;
561 } else if (cuttimes
!= loend
&& *loend
== ','
562 && (hi
= strtoimax(loend
+ 1, &hiend
, 10),
563 loend
+ 1 != hiend
&& !*hiend
)) {
564 if (cutlotime
< lo
) {
565 if (absolute_max_time
< lo
)
566 lo
= absolute_max_time
;
569 if (hi
< cuthitime
) {
570 if (hi
< absolute_min_time
+ 1)
571 hi
= absolute_min_time
+ 1;
576 _("%s: wild -t argument %s\n"),
583 if (iflag
| vflag
| Vflag
)
590 for (i
= optind
; i
< argc
; i
++) {
591 size_t arglen
= strlen(argv
[i
]);
592 if (longest
< arglen
)
593 longest
= min(arglen
, INT_MAX
);
596 for (i
= optind
; i
< argc
; ++i
) {
597 timezone_t tz
= tzalloc(argv
[i
]);
603 char const *e
= strerror(errno
);
604 fprintf(stderr
, _("%s: unknown timezone '%s': %s\n"),
605 progname
, argv
[i
], e
);
609 show(tz
, argv
[i
], now
, false);
614 t
= absolute_min_time
;
615 if (! (iflag
| Vflag
)) {
616 show(tz
, argv
[i
], t
, true);
617 if (my_localtime_rz(tz
, &t
, &tm
) == NULL
619 time_t newt
= cutlotime
;
620 if (my_localtime_rz(tz
, &newt
, &newtm
) != NULL
)
621 showextrema(tz
, argv
[i
], t
, NULL
, newt
);
624 if (t
+ 1 < cutlotime
)
626 tm_ok
= my_localtime_rz(tz
, &t
, &tm
) != NULL
;
628 ab
= saveabbr(&abbrev
, &abbrevsize
, &tm
);
630 showtrans("\nTZ=%f", &tm
, t
, ab
, argv
[i
]);
631 showtrans("-\t-\t%Q", &tm
, t
, ab
, argv
[i
]);
635 while (t
< cuthitime
- 1) {
636 time_t newt
= ((t
< absolute_max_time
- SECSPERDAY
/ 2
637 && t
+ SECSPERDAY
/ 2 < cuthitime
- 1)
640 struct tm
*newtmp
= localtime_rz(tz
, &newt
, &newtm
);
641 bool newtm_ok
= newtmp
!= NULL
;
642 if (tm_ok
!= newtm_ok
643 || (ab
&& (delta(&newtm
, &tm
) != newt
- t
644 || newtm
.tm_isdst
!= tm
.tm_isdst
645 || strcmp(abbr(&newtm
), ab
) != 0))) {
646 newt
= hunt(tz
, t
, newt
, false);
647 newtmp
= localtime_rz(tz
, &newt
, &newtm
);
648 newtm_ok
= newtmp
!= NULL
;
650 showtrans("%Y-%m-%d\t%L\t%Q", newtmp
, newt
,
651 newtm_ok
? abbr(&newtm
) : NULL
, argv
[i
]);
653 show(tz
, argv
[i
], newt
- 1, true);
654 show(tz
, argv
[i
], newt
, true);
660 ab
= saveabbr(&abbrev
, &abbrevsize
, &newtm
);
664 if (! (iflag
| Vflag
)) {
665 time_t newt
= absolute_max_time
;
668 struct tm
*tmp
= my_localtime_rz(tz
, &t
, &tm
);
670 && my_localtime_rz(tz
, &newt
, &newtm
) == NULL
)
671 showextrema(tz
, argv
[i
], t
, tmp
, newt
);
673 show(tz
, argv
[i
], absolute_max_time
, true);
678 if (errout
&& (ferror(stderr
) || fclose(stderr
) != 0))
686 register intmax_t myy
, seconds
, years
;
692 if (SECSPER400YEARS_FITS
&& 400 <= y
- myy
) {
693 intmax_t diff400
= (y
- myy
) / 400;
694 if (INTMAX_MAX
/ SECSPER400YEARS
< diff400
)
695 return absolute_max_time
;
696 seconds
= diff400
* SECSPER400YEARS
;
697 years
= diff400
* 400;
699 seconds
= isleap(myy
) ? SECSPERLYEAR
: SECSPERNYEAR
;
703 if (t
> absolute_max_time
- seconds
)
704 return absolute_max_time
;
708 if (SECSPER400YEARS_FITS
&& y
+ 400 <= myy
&& myy
< 0) {
709 intmax_t diff400
= (myy
- y
) / 400;
710 if (INTMAX_MAX
/ SECSPER400YEARS
< diff400
)
711 return absolute_min_time
;
712 seconds
= diff400
* SECSPER400YEARS
;
713 years
= diff400
* 400;
715 seconds
= isleap(myy
- 1) ? SECSPERLYEAR
: SECSPERNYEAR
;
719 if (t
< absolute_min_time
+ seconds
)
720 return absolute_min_time
;
726 /* Search for a discontinuity in timezone TZ, in the
727 timestamps ranging from LOT through HIT. LOT and HIT disagree
728 about some aspect of timezone. If ONLY_OK, search only for
729 definedness changes, i.e., localtime succeeds on one side of the
730 transition but fails on the other side. Return the timestamp just
731 before the transition from LOT's settings. */
734 hunt(timezone_t tz
, time_t lot
, time_t hit
, bool only_ok
)
737 static ptrdiff_t loabsize
;
741 /* Convert LOT into a broken-down time here, even though our
742 caller already did that. On platforms without TM_ZONE,
743 tzname may have been altered since our caller broke down
744 LOT, and tzname needs to be changed back. */
745 bool lotm_ok
= my_localtime_rz(tz
, &lot
, &lotm
) != NULL
;
747 char const *ab
= lotm_ok
? saveabbr(&loab
, &loabsize
, &lotm
) : NULL
;
750 /* T = average of LOT and HIT, rounding down.
752 int rem_sum
= lot
% 2 + hit
% 2;
753 time_t t
= (rem_sum
== 2) - (rem_sum
< 0) + lot
/ 2 + hit
/ 2;
756 tm_ok
= my_localtime_rz(tz
, &t
, &tm
) != NULL
;
759 || (ab
&& tm
.tm_isdst
== lotm
.tm_isdst
760 && delta(&tm
, &lotm
) == t
- lot
761 && strcmp(abbr(&tm
), ab
) == 0))) {
771 ** Thanks to Paul Eggert for logic used in delta_nonneg.
775 delta_nonneg(struct tm
*newp
, struct tm
*oldp
)
777 intmax_t oldy
= oldp
->tm_year
;
778 int cycles
= (newp
->tm_year
- oldy
) / YEARSPERREPEAT
;
779 intmax_t sec
= SECSPERREPEAT
, result
= cycles
* sec
;
780 int tmy
= oldp
->tm_year
+ cycles
* YEARSPERREPEAT
;
781 for ( ; tmy
< newp
->tm_year
; ++tmy
)
782 result
+= DAYSPERNYEAR
+ isleap_sum(tmy
, TM_YEAR_BASE
);
783 result
+= newp
->tm_yday
- oldp
->tm_yday
;
784 result
*= HOURSPERDAY
;
785 result
+= newp
->tm_hour
- oldp
->tm_hour
;
786 result
*= MINSPERHOUR
;
787 result
+= newp
->tm_min
- oldp
->tm_min
;
788 result
*= SECSPERMIN
;
789 result
+= newp
->tm_sec
- oldp
->tm_sec
;
794 delta(struct tm
*newp
, struct tm
*oldp
)
796 return (newp
->tm_year
< oldp
->tm_year
797 ? -delta_nonneg(oldp
, newp
)
798 : delta_nonneg(newp
, oldp
));
802 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
803 Assume A and B differ by at most one year. */
805 adjusted_yday(struct tm
const *a
, struct tm
const *b
)
807 int yday
= a
->tm_yday
;
808 if (b
->tm_year
< a
->tm_year
)
809 yday
+= 365 + isleap_sum(b
->tm_year
, TM_YEAR_BASE
);
814 /* If A is the broken-down local time and B the broken-down UT for
815 the same instant, return A's UT offset in seconds, where positive
816 offsets are east of Greenwich. On failure, return LONG_MIN.
818 If T is nonnull, *T is the timestamp that corresponds to A; call
819 my_gmtime_r and use its result instead of B. Otherwise, B is the
820 possibly nonnull result of an earlier call to my_gmtime_r. */
822 gmtoff(struct tm
const *a
, ATTRIBUTE_MAYBE_UNUSED
time_t *t
,
823 ATTRIBUTE_MAYBE_UNUSED
struct tm
const *b
)
830 b
= my_gmtime_r(t
, &tm
);
834 int ayday
= adjusted_yday(a
, b
);
835 int byday
= adjusted_yday(b
, a
);
836 int days
= ayday
- byday
;
837 long hours
= a
->tm_hour
- b
->tm_hour
+ 24 * days
;
838 long minutes
= a
->tm_min
- b
->tm_min
+ 60 * hours
;
839 long seconds
= a
->tm_sec
- b
->tm_sec
+ 60 * minutes
;
846 show(timezone_t tz
, char *zone
, time_t t
, bool v
)
848 register struct tm
* tmp
;
849 register struct tm
* gmtmp
;
852 printf("%-*s ", longest
, zone
);
854 gmtmp
= my_gmtime_r(&t
, &gmtm
);
856 printf(tformat(), t
);
857 printf(_(" (gmtime failed)"));
864 tmp
= my_localtime_rz(tz
, &t
, &tm
);
866 printf(tformat(), t
);
867 printf(_(" (localtime failed)"));
870 if (*abbr(tmp
) != '\0')
871 printf(" %s", abbr(tmp
));
873 long off
= gmtoff(tmp
, NULL
, gmtmp
);
874 printf(" isdst=%d", tmp
->tm_isdst
);
876 printf(" gmtoff=%ld", off
);
880 if (tmp
!= NULL
&& *abbr(tmp
) != '\0')
881 abbrok(abbr(tmp
), zone
);
884 /* Show timestamps just before and just after a transition between
885 defined and undefined (or vice versa) in either localtime or
886 gmtime. These transitions are for timezone TZ with name ZONE, in
887 the range from LO (with broken-down time LOTMP if that is nonnull)
888 through HI. LO and HI disagree on definedness. */
891 showextrema(timezone_t tz
, char *zone
, time_t lo
, struct tm
*lotmp
, time_t hi
)
893 struct tm localtm
[2], gmtm
[2];
894 time_t t
, boundary
= hunt(tz
, lo
, hi
, true);
896 hi
= (SECSPERDAY
< hi
- boundary
897 ? boundary
+ SECSPERDAY
898 : hi
+ (hi
< TIME_T_MAX
));
899 if (SECSPERDAY
< boundary
- lo
) {
900 lo
= boundary
- SECSPERDAY
;
901 lotmp
= my_localtime_rz(tz
, &lo
, &localtm
[old
]);
904 localtm
[old
] = *lotmp
;
906 localtm
[old
].tm_sec
= -1;
907 if (! my_gmtime_r(&lo
, &gmtm
[old
]))
908 gmtm
[old
].tm_sec
= -1;
910 /* Search sequentially for definedness transitions. Although this
911 could be sped up by refining 'hunt' to search for either
912 localtime or gmtime definedness transitions, it hardly seems
913 worth the trouble. */
914 for (t
= lo
+ 1; t
< hi
; t
++) {
916 if (! my_localtime_rz(tz
, &t
, &localtm
[new]))
917 localtm
[new].tm_sec
= -1;
918 if (! my_gmtime_r(&t
, &gmtm
[new]))
919 gmtm
[new].tm_sec
= -1;
920 if (((localtm
[old
].tm_sec
< 0) != (localtm
[new].tm_sec
< 0))
921 | ((gmtm
[old
].tm_sec
< 0) != (gmtm
[new].tm_sec
< 0))) {
922 show(tz
, zone
, t
- 1, true);
923 show(tz
, zone
, t
, true);
930 # define my_snprintf snprintf
934 /* A substitute for snprintf that is good enough for zdump. */
935 ATTRIBUTE_FORMAT((printf
, 3, 4)) static int
936 my_snprintf(char *s
, size_t size
, char const *format
, ...)
943 va_start(args
, format
);
944 if (strcmp(format
, "%s") == 0) {
945 arg
= va_arg(args
, char const *);
946 arglen
= strlen(arg
);
948 n
= vsprintf(buf
, format
, args
);
956 slen
= arglen
< size
? arglen
: size
- 1;
957 memcpy(s
, arg
, slen
);
959 n
= arglen
<= INT_MAX
? arglen
: -1;
965 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
966 Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit
967 :MM too if MM is also zero.
969 Return the length of the resulting string. If the string does not
970 fit, return the length that the string would have been if it had
971 fit; do not overrun the output buffer. */
973 format_local_time(char *buf
, ptrdiff_t size
, struct tm
const *tm
)
975 int ss
= tm
->tm_sec
, mm
= tm
->tm_min
, hh
= tm
->tm_hour
;
977 ? my_snprintf(buf
, size
, "%02d:%02d:%02d", hh
, mm
, ss
)
979 ? my_snprintf(buf
, size
, "%02d:%02d", hh
, mm
)
980 : my_snprintf(buf
, size
, "%02d", hh
));
983 /* Store into BUF, of size SIZE, a formatted UT offset for the
984 localtime *TM corresponding to time T. Use ISO 8601 format
985 +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
986 format -00 for unknown UT offsets. If the hour needs more than
987 two digits to represent, extend the length of HH as needed.
988 Otherwise, omit SS if SS is zero, and omit MM too if MM is also
991 Return the length of the resulting string, or -1 if the result is
992 not representable as a string. If the string does not fit, return
993 the length that the string would have been if it had fit; do not
994 overrun the output buffer. */
996 format_utc_offset(char *buf
, ptrdiff_t size
, struct tm
const *tm
, time_t t
)
998 long off
= gmtoff(tm
, &t
, NULL
);
999 char sign
= ((off
< 0
1001 && (*abbr(tm
) == '-' || strcmp(abbr(tm
), "zzz") == 0)))
1007 if (off
== LONG_MIN
)
1014 return (ss
|| 100 <= hh
1015 ? my_snprintf(buf
, size
, "%c%02ld%02d%02d", sign
, hh
, mm
, ss
)
1017 ? my_snprintf(buf
, size
, "%c%02ld%02d", sign
, hh
, mm
)
1018 : my_snprintf(buf
, size
, "%c%02ld", sign
, hh
));
1021 /* Store into BUF (of size SIZE) a quoted string representation of P.
1022 If the representation's length is less than SIZE, return the
1023 length; the representation is not null terminated. Otherwise
1024 return SIZE, to indicate that BUF is too small. */
1026 format_quoted_string(char *buf
, ptrdiff_t size
, char const *p
)
1038 default: *b
++ = c
, s
--; continue;
1039 case '\0': *b
++ = '"', s
--; return size
- s
;
1040 case '"': case '\\': break;
1041 case ' ': c
= 's'; break;
1042 case '\f': c
= 'f'; break;
1043 case '\n': c
= 'n'; break;
1044 case '\r': c
= 'r'; break;
1045 case '\t': c
= 't'; break;
1046 case '\v': c
= 'v'; break;
1048 *b
++ = '\\', *b
++ = c
, s
-= 2;
1052 /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
1053 TM is the broken-down time, T the seconds count, AB the time zone
1054 abbreviation, and ZONE_NAME the zone name. Return true if
1055 successful, false if the output would require more than SIZE bytes.
1056 TIME_FMT uses the same format that strftime uses, with these
1060 %L local time as per format_local_time
1061 %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
1062 and D is the isdst flag; except omit D if it is zero, omit %Z if
1063 it equals U, quote and escape %Z if it contains nonalphabetics,
1064 and omit any trailing tabs. */
1067 istrftime(char *buf
, ptrdiff_t size
, char const *time_fmt
,
1068 struct tm
const *tm
, time_t t
, char const *ab
, char const *zone_name
)
1072 char const *f
= time_fmt
, *p
;
1075 if (*p
== '%' && p
[1] == '%')
1079 && (p
[1] == 'f' || p
[1] == 'L' || p
[1] == 'Q'))) {
1080 ptrdiff_t formatted_len
;
1081 ptrdiff_t f_prefix_len
= p
- f
;
1082 ptrdiff_t f_prefix_copy_size
= sumsize(f_prefix_len
, 2);
1084 bool oversized
= sizeof fbuf
<= f_prefix_copy_size
;
1085 char *f_prefix_copy
= oversized
? xmalloc(f_prefix_copy_size
) : fbuf
;
1086 memcpy(f_prefix_copy
, f
, f_prefix_len
);
1087 strcpy(f_prefix_copy
+ f_prefix_len
, "X");
1088 formatted_len
= strftime(b
, s
, f_prefix_copy
, tm
);
1090 free(f_prefix_copy
);
1091 if (formatted_len
== 0)
1094 b
+= formatted_len
, s
-= formatted_len
;
1099 formatted_len
= format_quoted_string(b
, s
, zone_name
);
1102 formatted_len
= format_local_time(b
, s
, tm
);
1107 int offlen
= format_utc_offset(b
, s
, tm
, t
);
1108 if (! (0 <= offlen
&& offlen
< s
))
1110 show_abbr
= strcmp(b
, ab
) != 0;
1111 b
+= offlen
, s
-= offlen
;
1118 for (abp
= ab
; is_alpha(*abp
); abp
++)
1121 ? my_snprintf(b
, s
, "%s", ab
)
1122 : format_quoted_string(b
, s
, ab
));
1129 ? my_snprintf(b
, s
, &"\t\t%d"[show_abbr
], tm
->tm_isdst
)
1134 if (s
<= formatted_len
)
1136 b
+= formatted_len
, s
-= formatted_len
;
1143 /* Show a time transition. */
1145 showtrans(char const *time_fmt
, struct tm
const *tm
, time_t t
, char const *ab
,
1146 char const *zone_name
)
1149 printf(tformat(), t
);
1152 char stackbuf
[1000];
1153 ptrdiff_t size
= sizeof stackbuf
;
1154 char *buf
= stackbuf
;
1155 char *bufalloc
= NULL
;
1156 while (! istrftime(buf
, size
, time_fmt
, tm
, t
, ab
, zone_name
)) {
1157 size
= sumsize(size
, size
);
1159 buf
= bufalloc
= xmalloc(size
);
1167 abbr(struct tm
const *tmp
)
1170 return tmp
->TM_ZONE
;
1173 if (0 <= tmp
->tm_isdst
&& tzname
[0 < tmp
->tm_isdst
])
1174 return tzname
[0 < tmp
->tm_isdst
];
1181 ** The code below can fail on certain theoretical systems;
1182 ** it works on all known real-world systems as of 2022-01-25.
1189 /* C11-style _Generic is more likely to return the correct
1190 format when distinct types have the same size. */
1192 _Generic(+ (time_t) 0,
1193 int: "%d", long: "%ld", long long: "%lld",
1194 unsigned: "%u", unsigned long: "%lu",
1195 unsigned long long: "%llu",
1199 fmt
= _Generic((time_t) 0,
1200 intmax_t: "%"PRIdMAX
, uintmax_t: "%"PRIuMAX
,
1205 if (0 > (time_t) -1) { /* signed */
1206 if (sizeof(time_t) == sizeof(intmax_t))
1208 if (sizeof(time_t) > sizeof(long))
1210 if (sizeof(time_t) > sizeof(int))
1215 if (sizeof(time_t) == sizeof(uintmax_t))
1218 if (sizeof(time_t) > sizeof(unsigned long))
1220 if (sizeof(time_t) > sizeof(unsigned int))
1226 dumptime(register const struct tm
*timeptr
)
1228 static const char wday_name
[][4] = {
1229 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1231 static const char mon_name
[][4] = {
1232 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1233 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1240 ** The packaged localtime_rz and gmtime_r never put out-of-range
1241 ** values in tm_wday or tm_mon, but since this code might be compiled
1242 ** with other (perhaps experimental) versions, paranoia is in order.
1244 printf("%s %s%3d %.2d:%.2d:%.2d ",
1245 ((0 <= timeptr
->tm_wday
1246 && timeptr
->tm_wday
< sizeof wday_name
/ sizeof wday_name
[0])
1247 ? wday_name
[timeptr
->tm_wday
] : "???"),
1248 ((0 <= timeptr
->tm_mon
1249 && timeptr
->tm_mon
< sizeof mon_name
/ sizeof mon_name
[0])
1250 ? mon_name
[timeptr
->tm_mon
] : "???"),
1251 timeptr
->tm_mday
, timeptr
->tm_hour
,
1252 timeptr
->tm_min
, timeptr
->tm_sec
);
1253 trail
= timeptr
->tm_year
% DIVISOR
+ TM_YEAR_BASE
% DIVISOR
;
1254 lead
= timeptr
->tm_year
/ DIVISOR
+ TM_YEAR_BASE
/ DIVISOR
+
1257 if (trail
< 0 && lead
> 0) {
1260 } else if (lead
< 0 && trail
> 0) {
1265 printf("%d", trail
);
1266 else printf("%d%d", lead
, ((trail
< 0) ? -trail
: trail
));