diff options
Diffstat (limited to 'timezone/zdump.c')
-rw-r--r-- | timezone/zdump.c | 541 |
1 files changed, 305 insertions, 236 deletions
diff --git a/timezone/zdump.c b/timezone/zdump.c index 063a2635ec..bf75800101 100644 --- a/timezone/zdump.c +++ b/timezone/zdump.c @@ -19,89 +19,7 @@ # define USE_LTZ 1 #endif -#if USE_LTZ -# include "private.h" -#endif - -/* Enable tm_gmtoff and tm_zone on GNUish systems. */ -#define _GNU_SOURCE 1 -/* Enable strtoimax on Solaris 10. */ -#define __EXTENSIONS__ 1 - -#include "stdio.h" /* for stdout, stderr, perror */ -#include "string.h" /* for strcpy */ -#include "sys/types.h" /* for time_t */ -#include "time.h" /* for struct tm */ -#include "stdlib.h" /* for exit, malloc, atoi */ -#include "limits.h" /* for CHAR_BIT, LLONG_MAX */ -#include <errno.h> - -/* -** Substitutes for pre-C99 compilers. -** Much of this section of code is stolen from private.h. -*/ - -#ifndef HAVE_STDINT_H -# define HAVE_STDINT_H \ - (199901 <= __STDC_VERSION__ \ - || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__) -#endif -#if HAVE_STDINT_H -# include "stdint.h" -#endif -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include <inttypes.h> -#endif - -#ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 -typedef long int_fast32_t; -# else -typedef int int_fast32_t; -# endif -#endif - -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ -#if !defined LLONG_MAX && defined __LONG_LONG_MAX__ -# define LLONG_MAX __LONG_LONG_MAX__ -#endif - -#ifndef INTMAX_MAX -# ifdef LLONG_MAX -typedef long long intmax_t; -# define strtoimax strtoll -# define INTMAX_MAX LLONG_MAX -# else -typedef long intmax_t; -# define strtoimax strtol -# define INTMAX_MAX LONG_MAX -# endif -#endif - -#ifndef PRIdMAX -# if INTMAX_MAX == LLONG_MAX -# define PRIdMAX "lld" -# else -# define PRIdMAX "ld" -# endif -#endif - -/* Infer TM_ZONE on systems where this information is known, but suppress - guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ -#if (defined __GLIBC__ \ - || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ - || (defined __APPLE__ && defined __MACH__)) -# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF -# define TM_GMTOFF tm_gmtoff -# endif -# if !defined TM_ZONE && !defined NO_TM_ZONE -# define TM_ZONE tm_zone -# endif -#endif +#include "private.h" #ifndef HAVE_LOCALTIME_R # define HAVE_LOCALTIME_R 1 @@ -131,62 +49,6 @@ typedef long intmax_t; #define MAX_STRING_LENGTH 1024 #endif /* !defined MAX_STRING_LENGTH */ -#if __STDC_VERSION__ < 199901 -# define true 1 -# define false 0 -# define bool int -#else -# include <stdbool.h> -#endif - -#ifndef EXIT_SUCCESS -#define EXIT_SUCCESS 0 -#endif /* !defined EXIT_SUCCESS */ - -#ifndef EXIT_FAILURE -#define EXIT_FAILURE 1 -#endif /* !defined EXIT_FAILURE */ - -#ifndef SECSPERMIN -#define SECSPERMIN 60 -#endif /* !defined SECSPERMIN */ - -#ifndef MINSPERHOUR -#define MINSPERHOUR 60 -#endif /* !defined MINSPERHOUR */ - -#ifndef SECSPERHOUR -#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#endif /* !defined SECSPERHOUR */ - -#ifndef HOURSPERDAY -#define HOURSPERDAY 24 -#endif /* !defined HOURSPERDAY */ - -#ifndef EPOCH_YEAR -#define EPOCH_YEAR 1970 -#endif /* !defined EPOCH_YEAR */ - -#ifndef TM_YEAR_BASE -#define TM_YEAR_BASE 1900 -#endif /* !defined TM_YEAR_BASE */ - -#ifndef DAYSPERNYEAR -#define DAYSPERNYEAR 365 -#endif /* !defined DAYSPERNYEAR */ - -#ifndef isleap -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) -#endif /* !defined isleap */ - -#ifndef isleap_sum -/* -** See tzfile.h for details on isleap_sum. -*/ -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) -#endif /* !defined isleap_sum */ - -#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR) #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY) #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \ @@ -201,49 +63,24 @@ typedef long intmax_t; */ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 }; -#ifndef HAVE_GETTEXT -#define HAVE_GETTEXT 0 -#endif #if HAVE_GETTEXT -#include "locale.h" /* for setlocale */ -#include "libintl.h" +#include <locale.h> /* for setlocale */ #endif /* HAVE_GETTEXT */ -#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__) -# define ATTRIBUTE_PURE __attribute__ ((__pure__)) -#else -# define ATTRIBUTE_PURE /* empty */ -#endif - -/* -** For the benefit of GNU folk... -** '_(MSGID)' uses the current locale's message library string for MSGID. -** The default is to use gettext if available, and use MSGID otherwise. -*/ - -#ifndef _ -#if HAVE_GETTEXT -#define _(msgid) gettext(msgid) -#else /* !HAVE_GETTEXT */ -#define _(msgid) msgid -#endif /* !HAVE_GETTEXT */ -#endif /* !defined _ */ - -#if !defined TZ_DOMAIN && defined HAVE_GETTEXT -# define TZ_DOMAIN "tz" -#endif - #if ! HAVE_LOCALTIME_RZ # undef timezone_t # define timezone_t char ** #endif extern char ** environ; + +#if !HAVE_POSIX_DECLS extern int getopt(int argc, char * const argv[], const char * options); extern char * optarg; extern int optind; -extern char * tzname[2]; +extern char * tzname[]; +#endif /* The minimum and maximum finite time values. */ enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 }; @@ -266,6 +103,8 @@ static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE; static void dumptime(struct tm const *); static time_t hunt(timezone_t, char *, time_t, time_t); static void show(timezone_t, char *, time_t, bool); +static void showtrans(char const *, struct tm const *, time_t, char const *, + char const *); static const char *tformat(void); static time_t yeartot(intmax_t) ATTRIBUTE_PURE; @@ -303,6 +142,19 @@ sumsize(size_t a, size_t b) return sum; } +/* Return a pointer to a newly allocated buffer of size SIZE, exiting + on failure. SIZE should be nonzero. */ +static void * +xmalloc(size_t size) +{ + void *p = malloc(size); + if (!p) { + perror(progname); + exit(EXIT_FAILURE); + } + return p; +} + #if ! HAVE_TZSET # undef tzset # define tzset zdump_tzset @@ -390,21 +242,13 @@ tzalloc(char const *val) while (*e++) continue; - env = malloc(sumsize(sizeof *environ, - (e - environ) * sizeof *environ)); - if (! env) { - perror(progname); - exit(EXIT_FAILURE); - } + env = xmalloc(sumsize(sizeof *environ, + (e - environ) * sizeof *environ)); to = 1; for (e = environ; (env[to] = *e); e++) to += strncmp(*e, "TZ=", 3) != 0; } - env0 = malloc(sumsize(sizeof "TZ=", strlen(val))); - if (! env0) { - perror(progname); - exit(EXIT_FAILURE); - } + env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val))); env[0] = strcat(strcpy(env0, "TZ="), val); environ = fakeenv = env; tzset(); @@ -525,11 +369,7 @@ saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp) to avoid O(N**2) behavior on repeated calls. */ *bufalloc = sumsize(*bufalloc, ablen + 1); - *buf = malloc(*bufalloc); - if (! *buf) { - perror(progname); - exit(EXIT_FAILURE); - } + *buf = xmalloc(*bufalloc); } return strcpy(*buf, ab); } @@ -550,10 +390,18 @@ static void usage(FILE * const stream, const int status) { fprintf(stream, -_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n" +_("%s: usage: %s OPTIONS ZONENAME ...\n" + "Options include:\n" + " -c [L,]U Start at year L (default -500), end before year U (default 2500)\n" + " -t [L,]U Start at time L, end before time U (in seconds since 1970)\n" + " -i List transitions briefly (format is experimental)\n" \ + " -v List transitions verbosely\n" + " -V List transitions a bit less verbosely\n" + " --help Output this help\n" + " --version Output version info\n" "\n" "Report bugs to %s.\n"), - progname, progname, REPORT_BUGS_TO); + progname, progname, REPORT_BUGS_TO); if (status == EXIT_SUCCESS) close_file(stream); exit(status); @@ -565,7 +413,6 @@ main(int argc, char *argv[]) /* These are static so that they're initially zero. */ static char * abbrev; static size_t abbrevsize; - static struct tm newtm; register int i; register bool vflag; @@ -575,11 +422,7 @@ main(int argc, char *argv[]) register time_t cutlotime; register time_t cuthitime; time_t now; - time_t t; - time_t newt; - struct tm tm; - register struct tm * tmp; - register struct tm * newtmp; + bool iflag = false; cutlotime = absolute_min_time; cuthitime = absolute_max_time; @@ -601,9 +444,10 @@ main(int argc, char *argv[]) vflag = Vflag = false; cutarg = cuttimes = NULL; for (;;) - switch (getopt(argc, argv, "c:t:vV")) { + switch (getopt(argc, argv, "c:it:vV")) { case 'c': cutarg = optarg; break; case 't': cuttimes = optarg; break; + case 'i': iflag = true; break; case 'v': vflag = true; break; case 'V': Vflag = true; break; case -1: @@ -615,7 +459,7 @@ main(int argc, char *argv[]) } arg_processing_done:; - if (vflag | Vflag) { + if (iflag | vflag | Vflag) { intmax_t lo; intmax_t hi; char *loend, *hiend; @@ -672,7 +516,9 @@ main(int argc, char *argv[]) } } gmtzinit(); - now = time(NULL); + INITIALIZE (now); + if (! (iflag | vflag | Vflag)) + now = time(NULL); longest = 0; for (i = optind; i < argc; i++) { size_t arglen = strlen(argv[i]); @@ -683,48 +529,66 @@ main(int argc, char *argv[]) for (i = optind; i < argc; ++i) { timezone_t tz = tzalloc(argv[i]); char const *ab; + time_t t; + struct tm tm, newtm; + bool tm_ok; if (!tz) { perror(argv[i]); return EXIT_FAILURE; } - if (! (vflag | Vflag)) { + if (! (iflag | vflag | Vflag)) { show(tz, argv[i], now, false); tzfree(tz); continue; } warned = false; t = absolute_min_time; - if (!Vflag) { + if (! (iflag | Vflag)) { show(tz, argv[i], t, true); t += SECSPERDAY; show(tz, argv[i], t, true); } if (t < cutlotime) t = cutlotime; - tmp = my_localtime_rz(tz, &t, &tm); - if (tmp) + tm_ok = my_localtime_rz(tz, &t, &tm) != NULL; + if (tm_ok) { ab = saveabbr(&abbrev, &abbrevsize, &tm); + if (iflag) { + showtrans("\nTZ=%f", &tm, t, ab, argv[i]); + showtrans("-\t-\t%Q", &tm, t, ab, argv[i]); + } + } while (t < cuthitime) { - newt = ((t < absolute_max_time - SECSPERDAY / 2 - && t + SECSPERDAY / 2 < cuthitime) - ? t + SECSPERDAY / 2 - : cuthitime); - newtmp = localtime_rz(tz, &newt, &newtm); - if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) : - (delta(&newtm, &tm) != (newt - t) || - newtm.tm_isdst != tm.tm_isdst || - strcmp(abbr(&newtm), ab) != 0)) { - newt = hunt(tz, argv[i], t, newt); - newtmp = localtime_rz(tz, &newt, &newtm); - if (newtmp) - ab = saveabbr(&abbrev, &abbrevsize, - &newtm); - } - t = newt; - tm = newtm; - tmp = newtmp; + time_t newt = ((t < absolute_max_time - SECSPERDAY / 2 + && t + SECSPERDAY / 2 < cuthitime) + ? t + SECSPERDAY / 2 + : cuthitime); + struct tm *newtmp = localtime_rz(tz, &newt, &newtm); + bool newtm_ok = newtmp != NULL; + if (! (tm_ok & newtm_ok + ? (delta(&newtm, &tm) == newt - t + && newtm.tm_isdst == tm.tm_isdst + && strcmp(abbr(&newtm), ab) == 0) + : tm_ok == newtm_ok)) { + newt = hunt(tz, argv[i], t, newt); + newtmp = localtime_rz(tz, &newt, &newtm); + newtm_ok = newtmp != NULL; + if (iflag) + showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt, + newtm_ok ? abbr(&newtm) : NULL, argv[i]); + else { + show(tz, argv[i], newt - 1, true); + show(tz, argv[i], newt, true); + } + } + t = newt; + tm_ok = newtm_ok; + if (newtm_ok) { + ab = saveabbr(&abbrev, &abbrevsize, &newtm); + tm = newtm; + } } - if (!Vflag) { + if (! (iflag | Vflag)) { t = absolute_max_time; t -= SECSPERDAY; show(tz, argv[i], t, true); @@ -790,12 +654,11 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit) char const * ab; time_t t; struct tm lotm; - register struct tm * lotmp; struct tm tm; - register struct tm * tmp; + bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL; + bool tm_ok; - lotmp = my_localtime_rz(tz, &lot, &lotm); - if (lotmp) + if (lotm_ok) ab = saveabbr(&loab, &loabsize, &lotm); for ( ; ; ) { time_t diff = hit - lot; @@ -807,18 +670,17 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit) ++t; else if (t >= hit) --t; - tmp = my_localtime_rz(tz, &t, &tm); - if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) : - (delta(&tm, &lotm) == (t - lot) && - tm.tm_isdst == lotm.tm_isdst && - strcmp(abbr(&tm), ab) == 0)) { - lot = t; - lotm = tm; - lotmp = tmp; + tm_ok = my_localtime_rz(tz, &t, &tm) != NULL; + if (lotm_ok & tm_ok + ? (delta(&tm, &lotm) == t - lot + && tm.tm_isdst == lotm.tm_isdst + && strcmp(abbr(&tm), ab) == 0) + : lotm_ok == tm_ok) { + lot = t; + if (tm_ok) + lotm = tm; } else hit = t; } - show(tz, name, lot, true); - show(tz, name, hit, true); return hit; } @@ -862,13 +724,20 @@ adjusted_yday(struct tm const *a, struct tm const *b) /* If A is the broken-down local time and B the broken-down UTC for the same instant, return A's UTC offset in seconds, where positive - offsets are east of Greenwich. On failure, return LONG_MIN. */ + offsets are east of Greenwich. On failure, return LONG_MIN. + + If T is nonnull, *T is the timestamp that corresponds to A; call + my_gmtime_r and use its result instead of B. Otherwise, B is the + possibly nonnull result of an earlier call to my_gmtime_r. */ static long -gmtoff(struct tm const *a, struct tm const *b) +gmtoff(struct tm const *a, time_t *t, struct tm const *b) { #ifdef TM_GMTOFF return a->TM_GMTOFF; #else + struct tm tm; + if (t) + b = my_gmtime_r(t, &tm); if (! b) return LONG_MIN; else { @@ -907,7 +776,7 @@ show(timezone_t tz, char *zone, time_t t, bool v) if (*abbr(tmp) != '\0') printf(" %s", abbr(tmp)); if (v) { - long off = gmtoff(tmp, gmtmp); + long off = gmtoff(tmp, NULL, gmtmp); printf(" isdst=%d", tmp->tm_isdst); if (off != LONG_MIN) printf(" gmtoff=%ld", off); @@ -918,6 +787,206 @@ show(timezone_t tz, char *zone, time_t t, bool v) abbrok(abbr(tmp), zone); } +/* Store into BUF, of size SIZE, a formatted local time taken from *TM. + Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit + :MM too if MM is also zero. + + Return the length of the resulting string. If the string does not + fit, return the length that the string would have been if it had + fit; do not overrun the output buffer. */ +static int +format_local_time(char *buf, size_t size, struct tm const *tm) +{ + int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour; + return (ss + ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss) + : mm + ? snprintf(buf, size, "%02d:%02d", hh, mm) + : snprintf(buf, size, "%02d", hh)); +} + +/* Store into BUF, of size SIZE, a formatted UTC offset for the + localtime *TM corresponding to time T. Use ISO 8601 format + +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the + format -00 for unknown UTC offsets. If the hour needs more than + two digits to represent, extend the length of HH as needed. + Otherwise, omit SS if SS is zero, and omit MM too if MM is also + zero. + + Return the length of the resulting string, or -1 if the result is + not representable as a string. If the string does not fit, return + the length that the string would have been if it had fit; do not + overrun the output buffer. */ +static int +format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t) +{ + long off = gmtoff(tm, &t, NULL); + char sign = ((off < 0 + || (off == 0 + && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0))) + ? '-' : '+'); + long hh; + int mm, ss; + if (off < 0) + { + if (off == LONG_MIN) + return -1; + off = -off; + } + ss = off % 60; + mm = off / 60 % 60; + hh = off / 60 / 60; + return (ss || 100 <= hh + ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss) + : mm + ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm) + : snprintf(buf, size, "%c%02ld", sign, hh)); +} + +/* Store into BUF (of size SIZE) a quoted string representation of P. + If the representation's length is less than SIZE, return the + length; the representation is not null terminated. Otherwise + return SIZE, to indicate that BUF is too small. */ +static size_t +format_quoted_string(char *buf, size_t size, char const *p) +{ + char *b = buf; + size_t s = size; + if (!s) + return size; + *b++ = '"', s--; + for (;;) { + char c = *p++; + if (s <= 1) + return size; + switch (c) { + default: *b++ = c, s--; continue; + case '\0': *b++ = '"', s--; return size - s; + case '"': case '\\': break; + case ' ': c = 's'; break; + case '\f': c = 'f'; break; + case '\n': c = 'n'; break; + case '\r': c = 'r'; break; + case '\t': c = 't'; break; + case '\v': c = 'v'; break; + } + *b++ = '\\', *b++ = c, s -= 2; + } +} + +/* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT. + TM is the broken-down time, T the seconds count, AB the time zone + abbreviation, and ZONE_NAME the zone name. Return true if + successful, false if the output would require more than SIZE bytes. + TIME_FMT uses the same format that strftime uses, with these + additions: + + %f zone name + %L local time as per format_local_time + %Q like "U\t%Z\tD" where U is the UTC offset as for format_utc_offset + and D is the isdst flag; except omit D if it is zero, omit %Z if + it equals U, quote and escape %Z if it contains nonalphabetics, + and omit any trailing tabs. */ + +static bool +istrftime(char *buf, size_t size, char const *time_fmt, + struct tm const *tm, time_t t, char const *ab, char const *zone_name) +{ + char *b = buf; + size_t s = size; + char const *f = time_fmt, *p; + + for (p = f; ; p++) + if (*p == '%' && p[1] == '%') + p++; + else if (!*p + || (*p == '%' + && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) { + size_t formatted_len; + size_t f_prefix_len = p - f; + size_t f_prefix_copy_size = p - f + 2; + char fbuf[100]; + bool oversized = sizeof fbuf <= f_prefix_copy_size; + char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf; + memcpy(f_prefix_copy, f, f_prefix_len); + strcpy(f_prefix_copy + f_prefix_len, "X"); + formatted_len = strftime(b, s, f_prefix_copy, tm); + if (oversized) + free(f_prefix_copy); + if (formatted_len == 0) + return false; + formatted_len--; + b += formatted_len, s -= formatted_len; + if (!*p++) + break; + switch (*p) { + case 'f': + formatted_len = format_quoted_string(b, s, zone_name); + break; + case 'L': + formatted_len = format_local_time(b, s, tm); + break; + case 'Q': + { + bool show_abbr; + int offlen = format_utc_offset(b, s, tm, t); + if (! (0 <= offlen && offlen < s)) + return false; + show_abbr = strcmp(b, ab) != 0; + b += offlen, s -= offlen; + if (show_abbr) { + char const *abp; + size_t len; + if (s <= 1) + return false; + *b++ = '\t', s--; + for (abp = ab; is_alpha(*abp); abp++) + continue; + len = (!*abp && *ab + ? snprintf(b, s, "%s", ab) + : format_quoted_string(b, s, ab)); + if (s <= len) + return false; + b += len, s -= len; + } + formatted_len = (tm->tm_isdst + ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst) + : 0); + } + break; + } + if (s <= formatted_len) + return false; + b += formatted_len, s -= formatted_len; + f = p + 1; + } + *b = '\0'; + return true; +} + +/* Show a time transition. */ +static void +showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab, + char const *zone_name) +{ + if (!tm) { + printf(tformat(), t); + putchar('\n'); + } else { + char stackbuf[1000]; + size_t size = sizeof stackbuf; + char *buf = stackbuf; + char *bufalloc = NULL; + while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) { + size = sumsize(size, size); + free(bufalloc); + buf = bufalloc = xmalloc(size); + } + puts(buf); + free(bufalloc); + } +} + static char const * abbr(struct tm const *tmp) { |