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
24 #ifndef HAVE_LOCALTIME_R
25 # define HAVE_LOCALTIME_R 1
28 #ifndef HAVE_LOCALTIME_RZ
30 # define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
32 # define HAVE_LOCALTIME_RZ 0
41 #define ZDUMP_LO_YEAR (-500)
42 #endif /* !defined ZDUMP_LO_YEAR */
45 #define ZDUMP_HI_YEAR 2500
46 #endif /* !defined ZDUMP_HI_YEAR */
48 #ifndef MAX_STRING_LENGTH
49 #define MAX_STRING_LENGTH 1024
50 #endif /* !defined MAX_STRING_LENGTH */
52 #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
53 #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
54 #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
55 + SECSPERLYEAR * (intmax_t) (100 - 3))
58 ** True if SECSPER400YEARS is known to be representable as an
59 ** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
60 ** even if SECSPER400YEARS is representable, because when that happens
61 ** the code merely runs a bit more slowly, and this slowness doesn't
62 ** occur on any practical platform.
64 enum { SECSPER400YEARS_FITS
= SECSPERLYEAR
<= INTMAX_MAX
/ 400 };
67 #include <locale.h> /* for setlocale */
68 #endif /* HAVE_GETTEXT */
70 #if ! HAVE_LOCALTIME_RZ
72 # define timezone_t char **
75 extern char ** environ
;
78 extern int getopt(int argc
, char * const argv
[],
79 const char * options
);
82 extern char * tzname
[];
85 /* The minimum and maximum finite time values. */
86 enum { atime_shift
= CHAR_BIT
* sizeof (time_t) - 2 };
87 static time_t const absolute_min_time
=
89 ? (- ((time_t) ~ (time_t) 0 < 0)
90 - (((time_t) 1 << atime_shift
) - 1 + ((time_t) 1 << atime_shift
)))
92 static time_t const absolute_max_time
=
94 ? (((time_t) 1 << atime_shift
) - 1 + ((time_t) 1 << atime_shift
))
97 static char * progname
;
101 static char const *abbr(struct tm
const *);
102 static intmax_t delta(struct tm
*, struct tm
*) ATTRIBUTE_PURE
;
103 static void dumptime(struct tm
const *);
104 static time_t hunt(timezone_t
, char *, time_t, time_t);
105 static void show(timezone_t
, char *, time_t, bool);
106 static void showtrans(char const *, struct tm
const *, time_t, char const *,
108 static const char *tformat(void);
109 static time_t yeartot(intmax_t) ATTRIBUTE_PURE
;
111 /* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
112 #define is_digit(c) ((unsigned)(c) - '0' <= 9)
114 /* Is A an alphabetic character in the C locale? */
121 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
122 case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
123 case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
124 case 'V': case 'W': case 'X': case 'Y': case 'Z':
125 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
126 case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
127 case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
128 case 'v': case 'w': case 'x': case 'y': case 'z':
133 /* Return A + B, exiting if the result would overflow. */
135 sumsize(size_t a
, size_t b
)
139 fprintf(stderr
, "%s: size overflow\n", progname
);
145 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
146 on failure. SIZE should be nonzero. */
150 void *p
= malloc(size
);
160 # define tzset zdump_tzset
161 static void tzset(void) { }
164 /* Assume gmtime_r works if localtime_r does.
165 A replacement localtime_r is defined below if needed. */
166 #if ! HAVE_LOCALTIME_R
169 # define gmtime_r zdump_gmtime_r
172 gmtime_r(time_t *tp
, struct tm
*tmp
)
174 struct tm
*r
= gmtime(tp
);
184 /* Platforms with TM_ZONE don't need tzname, so they can use the
185 faster localtime_rz or localtime_r if available. */
187 #if defined TM_ZONE && HAVE_LOCALTIME_RZ
188 # define USE_LOCALTIME_RZ true
190 # define USE_LOCALTIME_RZ false
193 #if ! USE_LOCALTIME_RZ
195 # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
197 # define localtime_r zdump_localtime_r
199 localtime_r(time_t *tp
, struct tm
*tmp
)
201 struct tm
*r
= localtime(tp
);
211 # define localtime_rz zdump_localtime_rz
213 localtime_rz(timezone_t rz
, time_t *tp
, struct tm
*tmp
)
215 return localtime_r(tp
, tmp
);
220 # define mktime_z zdump_mktime_z
222 mktime_z(timezone_t tz
, struct tm
*tmp
)
230 # define tzalloc zdump_tzalloc
231 # define tzfree zdump_tzfree
234 tzalloc(char const *val
)
236 static char **fakeenv
;
237 char **env
= fakeenv
;
245 env
= xmalloc(sumsize(sizeof *environ
,
246 (e
- environ
) * sizeof *environ
));
248 for (e
= environ
; (env
[to
] = *e
); e
++)
249 to
+= strncmp(*e
, "TZ=", 3) != 0;
251 env0
= xmalloc(sumsize(sizeof "TZ=", strlen(val
)));
252 env
[0] = strcat(strcpy(env0
, "TZ="), val
);
253 environ
= fakeenv
= env
;
259 tzfree(timezone_t env
)
264 #endif /* ! USE_LOCALTIME_RZ */
266 /* A UTC time zone, and its initializer. */
267 static timezone_t gmtz
;
271 if (USE_LOCALTIME_RZ
) {
272 static char const utc
[] = "UTC0";
281 /* Convert *TP to UTC, storing the broken-down time into *TMP.
282 Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
283 except typically faster if USE_LOCALTIME_RZ. */
285 my_gmtime_r(time_t *tp
, struct tm
*tmp
)
287 return USE_LOCALTIME_RZ
? localtime_rz(gmtz
, tp
, tmp
) : gmtime_r(tp
, tmp
);
291 # define my_localtime_rz localtime_rz
292 #else /* !defined TYPECHECK */
295 my_localtime_rz(timezone_t tz
, time_t *tp
, struct tm
*tmp
)
297 tmp
= localtime_rz(tz
, tp
, tmp
);
303 t
= mktime_z(tz
, &tm
);
306 fprintf(stderr
, "\n%s: ", progname
);
307 fprintf(stderr
, tformat(), *tp
);
308 fprintf(stderr
, " ->");
309 fprintf(stderr
, " year=%d", tmp
->tm_year
);
310 fprintf(stderr
, " mon=%d", tmp
->tm_mon
);
311 fprintf(stderr
, " mday=%d", tmp
->tm_mday
);
312 fprintf(stderr
, " hour=%d", tmp
->tm_hour
);
313 fprintf(stderr
, " min=%d", tmp
->tm_min
);
314 fprintf(stderr
, " sec=%d", tmp
->tm_sec
);
315 fprintf(stderr
, " isdst=%d", tmp
->tm_isdst
);
316 fprintf(stderr
, " -> ");
317 fprintf(stderr
, tformat(), t
);
318 fprintf(stderr
, "\n");
324 #endif /* !defined TYPECHECK */
327 abbrok(const char *const abbrp
, const char *const zone
)
329 register const char * cp
;
330 register const char * wp
;
335 while (is_alpha(*cp
) || is_digit(*cp
) || *cp
== '-' || *cp
== '+')
338 wp
= _("has fewer than 3 characters");
339 else if (cp
- abbrp
> 6)
340 wp
= _("has more than 6 characters");
342 wp
= _("has characters other than ASCII alphanumerics, '-' or '+'");
347 _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
348 progname
, zone
, abbrp
, wp
);
349 warned
= errout
= true;
352 /* Return a time zone abbreviation. If the abbreviation needs to be
353 saved, use *BUF (of size *BUFALLOC) to save it, and return the
354 abbreviation in the possibly-reallocated *BUF. Otherwise, just
355 return the abbreviation. Get the abbreviation from TMP.
356 Exit on memory allocation failure. */
358 saveabbr(char **buf
, size_t *bufalloc
, struct tm
const *tmp
)
360 char const *ab
= abbr(tmp
);
361 if (HAVE_LOCALTIME_RZ
)
364 size_t ablen
= strlen(ab
);
365 if (*bufalloc
<= ablen
) {
368 /* Make the new buffer at least twice as long as the old,
369 to avoid O(N**2) behavior on repeated calls. */
370 *bufalloc
= sumsize(*bufalloc
, ablen
+ 1);
372 *buf
= xmalloc(*bufalloc
);
374 return strcpy(*buf
, ab
);
379 close_file(FILE *stream
)
381 char const *e
= (ferror(stream
) ? _("I/O error")
382 : fclose(stream
) != 0 ? strerror(errno
) : NULL
);
384 fprintf(stderr
, "%s: %s\n", progname
, e
);
390 usage(FILE * const stream
, const int status
)
393 _("%s: usage: %s OPTIONS ZONENAME ...\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"
403 "Report bugs to %s.\n"),
404 progname
, progname
, REPORT_BUGS_TO
);
405 if (status
== EXIT_SUCCESS
)
411 main(int argc
, char *argv
[])
413 /* These are static so that they're initially zero. */
414 static char * abbrev
;
415 static size_t abbrevsize
;
420 register char * cutarg
;
421 register char * cuttimes
;
422 register time_t cutlotime
;
423 register time_t cuthitime
;
427 cutlotime
= absolute_min_time
;
428 cuthitime
= absolute_max_time
;
430 setlocale(LC_ALL
, "");
432 bindtextdomain(TZ_DOMAIN
, TZ_DOMAINDIR
);
433 #endif /* defined TEXTDOMAINDIR */
434 textdomain(TZ_DOMAIN
);
435 #endif /* HAVE_GETTEXT */
437 for (i
= 1; i
< argc
; ++i
)
438 if (strcmp(argv
[i
], "--version") == 0) {
439 printf("zdump %s%s\n", PKGVERSION
, TZVERSION
);
441 } else if (strcmp(argv
[i
], "--help") == 0) {
442 usage(stdout
, EXIT_SUCCESS
);
444 vflag
= Vflag
= false;
445 cutarg
= cuttimes
= NULL
;
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;
454 if (! (optind
== argc
- 1 && strcmp(argv
[optind
], "=") == 0))
455 goto arg_processing_done
;
458 usage(stderr
, EXIT_FAILURE
);
460 arg_processing_done
:;
462 if (iflag
| vflag
| Vflag
) {
466 register intmax_t cutloyear
= ZDUMP_LO_YEAR
;
467 register intmax_t cuthiyear
= ZDUMP_HI_YEAR
;
468 if (cutarg
!= NULL
) {
469 lo
= strtoimax(cutarg
, &loend
, 10);
470 if (cutarg
!= loend
&& !*loend
) {
473 } else if (cutarg
!= loend
&& *loend
== ','
474 && (hi
= strtoimax(loend
+ 1, &hiend
, 10),
475 loend
+ 1 != hiend
&& !*hiend
)) {
479 fprintf(stderr
, _("%s: wild -c argument %s\n"),
484 if (cutarg
!= NULL
|| cuttimes
== NULL
) {
485 cutlotime
= yeartot(cutloyear
);
486 cuthitime
= yeartot(cuthiyear
);
488 if (cuttimes
!= NULL
) {
489 lo
= strtoimax(cuttimes
, &loend
, 10);
490 if (cuttimes
!= loend
&& !*loend
) {
492 if (hi
< cuthitime
) {
493 if (hi
< absolute_min_time
)
494 hi
= absolute_min_time
;
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
;
505 if (hi
< cuthitime
) {
506 if (hi
< absolute_min_time
)
507 hi
= absolute_min_time
;
512 _("%s: wild -t argument %s\n"),
520 if (! (iflag
| vflag
| Vflag
))
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
;
529 for (i
= optind
; i
< argc
; ++i
) {
530 timezone_t tz
= tzalloc(argv
[i
]);
539 if (! (iflag
| vflag
| Vflag
)) {
540 show(tz
, argv
[i
], now
, false);
545 t
= absolute_min_time
;
546 if (! (iflag
| Vflag
)) {
547 show(tz
, argv
[i
], t
, true);
549 show(tz
, argv
[i
], t
, true);
553 tm_ok
= my_localtime_rz(tz
, &t
, &tm
) != NULL
;
555 ab
= saveabbr(&abbrev
, &abbrevsize
, &tm
);
557 showtrans("\nTZ=%f", &tm
, t
, ab
, argv
[i
]);
558 showtrans("-\t-\t%Q", &tm
, t
, ab
, argv
[i
]);
561 while (t
< cuthitime
) {
562 time_t newt
= ((t
< absolute_max_time
- SECSPERDAY
/ 2
563 && 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
;
577 showtrans("%Y-%m-%d\t%L\t%Q", newtmp
, newt
,
578 newtm_ok
? abbr(&newtm
) : NULL
, argv
[i
]);
580 show(tz
, argv
[i
], newt
- 1, true);
581 show(tz
, argv
[i
], newt
, true);
587 ab
= saveabbr(&abbrev
, &abbrevsize
, &newtm
);
591 if (! (iflag
| Vflag
)) {
592 t
= absolute_max_time
;
594 show(tz
, argv
[i
], t
, true);
596 show(tz
, argv
[i
], t
, true);
601 if (errout
&& (ferror(stderr
) || fclose(stderr
) != 0))
609 register intmax_t myy
, seconds
, years
;
615 if (SECSPER400YEARS_FITS
&& 400 <= y
- myy
) {
616 intmax_t diff400
= (y
- myy
) / 400;
617 if (INTMAX_MAX
/ SECSPER400YEARS
< diff400
)
618 return absolute_max_time
;
619 seconds
= diff400
* SECSPER400YEARS
;
620 years
= diff400
* 400;
622 seconds
= isleap(myy
) ? SECSPERLYEAR
: SECSPERNYEAR
;
626 if (t
> absolute_max_time
- seconds
)
627 return absolute_max_time
;
631 if (SECSPER400YEARS_FITS
&& y
+ 400 <= myy
&& myy
< 0) {
632 intmax_t diff400
= (myy
- y
) / 400;
633 if (INTMAX_MAX
/ SECSPER400YEARS
< diff400
)
634 return absolute_min_time
;
635 seconds
= diff400
* SECSPER400YEARS
;
636 years
= diff400
* 400;
638 seconds
= isleap(myy
- 1) ? SECSPERLYEAR
: SECSPERNYEAR
;
642 if (t
< absolute_min_time
+ seconds
)
643 return absolute_min_time
;
650 hunt(timezone_t tz
, char *name
, time_t lot
, time_t hit
)
653 static size_t loabsize
;
658 bool lotm_ok
= my_localtime_rz(tz
, &lot
, &lotm
) != NULL
;
662 ab
= saveabbr(&loab
, &loabsize
, &lotm
);
664 time_t diff
= hit
- lot
;
673 tm_ok
= my_localtime_rz(tz
, &t
, &tm
) != NULL
;
675 ? (delta(&tm
, &lotm
) == t
- lot
676 && tm
.tm_isdst
== lotm
.tm_isdst
677 && strcmp(abbr(&tm
), ab
) == 0)
678 : lotm_ok
== tm_ok
) {
688 ** Thanks to Paul Eggert for logic used in delta.
692 delta(struct tm
* newp
, struct tm
*oldp
)
694 register intmax_t result
;
697 if (newp
->tm_year
< oldp
->tm_year
)
698 return -delta(oldp
, newp
);
700 for (tmy
= oldp
->tm_year
; tmy
< newp
->tm_year
; ++tmy
)
701 result
+= DAYSPERNYEAR
+ isleap_sum(tmy
, TM_YEAR_BASE
);
702 result
+= newp
->tm_yday
- oldp
->tm_yday
;
703 result
*= HOURSPERDAY
;
704 result
+= newp
->tm_hour
- oldp
->tm_hour
;
705 result
*= MINSPERHOUR
;
706 result
+= newp
->tm_min
- oldp
->tm_min
;
707 result
*= SECSPERMIN
;
708 result
+= newp
->tm_sec
- oldp
->tm_sec
;
713 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
714 Assume A and B differ by at most one year. */
716 adjusted_yday(struct tm
const *a
, struct tm
const *b
)
718 int yday
= a
->tm_yday
;
719 if (b
->tm_year
< a
->tm_year
)
720 yday
+= 365 + isleap_sum(b
->tm_year
, TM_YEAR_BASE
);
725 /* If A is the broken-down local time and B the broken-down UTC for
726 the same instant, return A's UTC offset in seconds, where positive
727 offsets are east of Greenwich. On failure, return LONG_MIN.
729 If T is nonnull, *T is the timestamp that corresponds to A; call
730 my_gmtime_r and use its result instead of B. Otherwise, B is the
731 possibly nonnull result of an earlier call to my_gmtime_r. */
733 gmtoff(struct tm
const *a
, time_t *t
, struct tm
const *b
)
740 b
= my_gmtime_r(t
, &tm
);
744 int ayday
= adjusted_yday(a
, b
);
745 int byday
= adjusted_yday(b
, a
);
746 int days
= ayday
- byday
;
747 long hours
= a
->tm_hour
- b
->tm_hour
+ 24 * days
;
748 long minutes
= a
->tm_min
- b
->tm_min
+ 60 * hours
;
749 long seconds
= a
->tm_sec
- b
->tm_sec
+ 60 * minutes
;
756 show(timezone_t tz
, char *zone
, time_t t
, bool v
)
758 register struct tm
* tmp
;
759 register struct tm
* gmtmp
;
762 printf("%-*s ", longest
, zone
);
764 gmtmp
= my_gmtime_r(&t
, &gmtm
);
766 printf(tformat(), t
);
773 tmp
= my_localtime_rz(tz
, &t
, &tm
);
776 if (*abbr(tmp
) != '\0')
777 printf(" %s", abbr(tmp
));
779 long off
= gmtoff(tmp
, NULL
, gmtmp
);
780 printf(" isdst=%d", tmp
->tm_isdst
);
782 printf(" gmtoff=%ld", off
);
786 if (tmp
!= NULL
&& *abbr(tmp
) != '\0')
787 abbrok(abbr(tmp
), zone
);
790 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
791 Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit
792 :MM too if MM is also zero.
794 Return the length of the resulting string. If the string does not
795 fit, return the length that the string would have been if it had
796 fit; do not overrun the output buffer. */
798 format_local_time(char *buf
, size_t size
, struct tm
const *tm
)
800 int ss
= tm
->tm_sec
, mm
= tm
->tm_min
, hh
= tm
->tm_hour
;
802 ? snprintf(buf
, size
, "%02d:%02d:%02d", hh
, mm
, ss
)
804 ? snprintf(buf
, size
, "%02d:%02d", hh
, mm
)
805 : snprintf(buf
, size
, "%02d", hh
));
808 /* Store into BUF, of size SIZE, a formatted UTC offset for the
809 localtime *TM corresponding to time T. Use ISO 8601 format
810 +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
811 format -00 for unknown UTC offsets. If the hour needs more than
812 two digits to represent, extend the length of HH as needed.
813 Otherwise, omit SS if SS is zero, and omit MM too if MM is also
816 Return the length of the resulting string, or -1 if the result is
817 not representable as a string. If the string does not fit, return
818 the length that the string would have been if it had fit; do not
819 overrun the output buffer. */
821 format_utc_offset(char *buf
, size_t size
, struct tm
const *tm
, time_t t
)
823 long off
= gmtoff(tm
, &t
, NULL
);
824 char sign
= ((off
< 0
826 && (*abbr(tm
) == '-' || strcmp(abbr(tm
), "zzz") == 0)))
839 return (ss
|| 100 <= hh
840 ? snprintf(buf
, size
, "%c%02ld%02d%02d", sign
, hh
, mm
, ss
)
842 ? snprintf(buf
, size
, "%c%02ld%02d", sign
, hh
, mm
)
843 : snprintf(buf
, size
, "%c%02ld", sign
, hh
));
846 /* Store into BUF (of size SIZE) a quoted string representation of P.
847 If the representation's length is less than SIZE, return the
848 length; the representation is not null terminated. Otherwise
849 return SIZE, to indicate that BUF is too small. */
851 format_quoted_string(char *buf
, size_t size
, char const *p
)
863 default: *b
++ = c
, s
--; continue;
864 case '\0': *b
++ = '"', s
--; return size
- s
;
865 case '"': case '\\': break;
866 case ' ': c
= 's'; break;
867 case '\f': c
= 'f'; break;
868 case '\n': c
= 'n'; break;
869 case '\r': c
= 'r'; break;
870 case '\t': c
= 't'; break;
871 case '\v': c
= 'v'; break;
873 *b
++ = '\\', *b
++ = c
, s
-= 2;
877 /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
878 TM is the broken-down time, T the seconds count, AB the time zone
879 abbreviation, and ZONE_NAME the zone name. Return true if
880 successful, false if the output would require more than SIZE bytes.
881 TIME_FMT uses the same format that strftime uses, with these
885 %L local time as per format_local_time
886 %Q like "U\t%Z\tD" where U is the UTC offset as for format_utc_offset
887 and D is the isdst flag; except omit D if it is zero, omit %Z if
888 it equals U, quote and escape %Z if it contains nonalphabetics,
889 and omit any trailing tabs. */
892 istrftime(char *buf
, size_t size
, char const *time_fmt
,
893 struct tm
const *tm
, time_t t
, char const *ab
, char const *zone_name
)
897 char const *f
= time_fmt
, *p
;
900 if (*p
== '%' && p
[1] == '%')
904 && (p
[1] == 'f' || p
[1] == 'L' || p
[1] == 'Q'))) {
905 size_t formatted_len
;
906 size_t f_prefix_len
= p
- f
;
907 size_t f_prefix_copy_size
= p
- f
+ 2;
909 bool oversized
= sizeof fbuf
<= f_prefix_copy_size
;
910 char *f_prefix_copy
= oversized
? xmalloc(f_prefix_copy_size
) : fbuf
;
911 memcpy(f_prefix_copy
, f
, f_prefix_len
);
912 strcpy(f_prefix_copy
+ f_prefix_len
, "X");
913 formatted_len
= strftime(b
, s
, f_prefix_copy
, tm
);
916 if (formatted_len
== 0)
919 b
+= formatted_len
, s
-= formatted_len
;
924 formatted_len
= format_quoted_string(b
, s
, zone_name
);
927 formatted_len
= format_local_time(b
, s
, tm
);
932 int offlen
= format_utc_offset(b
, s
, tm
, t
);
933 if (! (0 <= offlen
&& offlen
< s
))
935 show_abbr
= strcmp(b
, ab
) != 0;
936 b
+= offlen
, s
-= offlen
;
943 for (abp
= ab
; is_alpha(*abp
); abp
++)
946 ? snprintf(b
, s
, "%s", ab
)
947 : format_quoted_string(b
, s
, ab
));
952 formatted_len
= (tm
->tm_isdst
953 ? snprintf(b
, s
, &"\t\t%d"[show_abbr
], tm
->tm_isdst
)
958 if (s
<= formatted_len
)
960 b
+= formatted_len
, s
-= formatted_len
;
967 /* Show a time transition. */
969 showtrans(char const *time_fmt
, struct tm
const *tm
, time_t t
, char const *ab
,
970 char const *zone_name
)
973 printf(tformat(), t
);
977 size_t size
= sizeof stackbuf
;
978 char *buf
= stackbuf
;
979 char *bufalloc
= NULL
;
980 while (! istrftime(buf
, size
, time_fmt
, tm
, t
, ab
, zone_name
)) {
981 size
= sumsize(size
, size
);
983 buf
= bufalloc
= xmalloc(size
);
991 abbr(struct tm
const *tmp
)
996 return (0 <= tmp
->tm_isdst
&& tzname
[0 < tmp
->tm_isdst
]
997 ? tzname
[0 < tmp
->tm_isdst
]
1003 ** The code below can fail on certain theoretical systems;
1004 ** it works on all known real-world systems as of 2004-12-30.
1010 if (0 > (time_t) -1) { /* signed */
1011 if (sizeof (time_t) == sizeof (intmax_t))
1013 if (sizeof (time_t) > sizeof (long))
1015 if (sizeof (time_t) > sizeof (int))
1020 if (sizeof (time_t) == sizeof (uintmax_t))
1023 if (sizeof (time_t) > sizeof (unsigned long))
1025 if (sizeof (time_t) > sizeof (unsigned int))
1031 dumptime(register const struct tm
*timeptr
)
1033 static const char wday_name
[][3] = {
1034 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1036 static const char mon_name
[][3] = {
1037 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1038 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1040 register const char * wn
;
1041 register const char * mn
;
1045 if (timeptr
== NULL
) {
1050 ** The packaged localtime_rz and gmtime_r never put out-of-range
1051 ** values in tm_wday or tm_mon, but since this code might be compiled
1052 ** with other (perhaps experimental) versions, paranoia is in order.
1054 if (timeptr
->tm_wday
< 0 || timeptr
->tm_wday
>=
1055 (int) (sizeof wday_name
/ sizeof wday_name
[0]))
1057 else wn
= wday_name
[timeptr
->tm_wday
];
1058 if (timeptr
->tm_mon
< 0 || timeptr
->tm_mon
>=
1059 (int) (sizeof mon_name
/ sizeof mon_name
[0]))
1061 else mn
= mon_name
[timeptr
->tm_mon
];
1062 printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
1064 timeptr
->tm_mday
, timeptr
->tm_hour
,
1065 timeptr
->tm_min
, timeptr
->tm_sec
);
1067 trail
= timeptr
->tm_year
% DIVISOR
+ TM_YEAR_BASE
% DIVISOR
;
1068 lead
= timeptr
->tm_year
/ DIVISOR
+ TM_YEAR_BASE
/ DIVISOR
+
1071 if (trail
< 0 && lead
> 0) {
1074 } else if (lead
< 0 && trail
> 0) {
1079 printf("%d", trail
);
1080 else printf("%d%d", lead
, ((trail
< 0) ? -trail
: trail
));