diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2024-04-06 23:39:53 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2024-04-07 13:35:48 -0700 |
commit | 1f94147a79fcb7211f1421b87383cad93986797f (patch) | |
tree | e75fd16819071d7cf0b84adafa81e56fb91d0f99 /timezone/zdump.c | |
parent | 57581acd9559217e859fdac693145ce6399f4d70 (diff) | |
download | glibc-1f94147a79fcb7211f1421b87383cad93986797f.zip glibc-1f94147a79fcb7211f1421b87383cad93986797f.tar.gz glibc-1f94147a79fcb7211f1421b87383cad93986797f.tar.bz2 |
timezone: sync to TZDB 2024a
Sync tzselect, zdump, zic to TZDB 2024a.
This patch incorporates the following TZDB source code changes,
listed roughly in descending order of importance.
zic now supports links to links, needed for future tzdata
zic now defaults to '-b slim'
zic now updates output files atomically
zic has new options -R, -l -, -p -
zic -r now uses -00 for unspecified timestamps
zdump now uses [lo,hi) for both -c and -t
Fix several integer overflow bugs
zic now checks input bytes more carefully
Simplify and fix new TZDIR setup
Default time_t to 64 bits on glibc 2.34+ 32-bit
zic now generates TZ strings that conform to POSIX when all-year DST
zic -v now shows extreme-int tm_year transitions
Fix zic bug in last time type of Asia/Gaza etc.
Fix zic bug with Palestine after 2075
Fix bug uncovered by recent change to Iran history
Fix 'zic -b fat' bug with Port Moresby 32-bit data
Fix zic bug with -r @X where X is deduced from TZ
Fix bug with zic -r cutoff before 1st transition
Fix leap second expiry and truncation
Fix zic bug on Linux 2.6.16 and 2.6.17
Fix bug with 'zic -d /a/b/c' if /a is unwriteable
Don't mistruncate TZif files at leap seconds
Fix zdump undefined behavior if !USE_LTZ
zdump -v reports localtime+gmtime failures better
Fix zdump diagnostic for missing timezone
Don't assume nonempty argv
Port better to C23
Do not assume negative >> behavior
I18nize zdump a bit better
Port zdump to right_only installations
New tzselect menu option 'now'
tzselect can now use current time to help choose
Improve tzselect behavior for Turkey etc.
tzselect: do not create temporary files
tzselect: work around mawk bug with {2,}
tzselect: Port to POSIX awk, which prohibits -v newlines
Do not use empty RE in tzselect
Don't set TZ in tzselect
Avoid sed, expr in tzselect
tzselect: Fix problems with spaces in TZDIR
Improve tzselect diagnostics
Remove zic workaround for Qt bug 53071
Remove zic support for "min" in Rule lines
Remove zic support for zic -y, Rule TYPEs, pacificnew
Remove tzselect workaround for Bash 1.14.7 bug
* SHARED-FILES: Update to match current sync.
* config.h.in (HAVE_STRERROR): Remove; no longer needed.
* timezone/Makefile ($(objpfx)zic.o): Depend on tzdir.h.
($(objpfx)tzdir.h): New rule to build a placeholder.
* timezone/private.h, timezone/tzfile.h, timezone/version:
* timezone/zdump.c, timezone/zic.c: Copy verbatim from TZDB 2024a.
Diffstat (limited to 'timezone/zdump.c')
-rw-r--r-- | timezone/zdump.c | 453 |
1 files changed, 300 insertions, 153 deletions
diff --git a/timezone/zdump.c b/timezone/zdump.c index b532fe3..7d99cc7 100644 --- a/timezone/zdump.c +++ b/timezone/zdump.c @@ -15,7 +15,7 @@ #include <stdio.h> #ifndef HAVE_SNPRINTF -# define HAVE_SNPRINTF (199901 <= __STDC_VERSION__) +# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) #endif #ifndef HAVE_LOCALTIME_R @@ -35,17 +35,13 @@ #endif #ifndef ZDUMP_LO_YEAR -#define ZDUMP_LO_YEAR (-500) +# define ZDUMP_LO_YEAR (-500) #endif /* !defined ZDUMP_LO_YEAR */ #ifndef ZDUMP_HI_YEAR -#define ZDUMP_HI_YEAR 2500 +# define ZDUMP_HI_YEAR 2500 #endif /* !defined ZDUMP_HI_YEAR */ -#ifndef MAX_STRING_LENGTH -#define MAX_STRING_LENGTH 1024 -#endif /* !defined MAX_STRING_LENGTH */ - #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR) #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY) #define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \ @@ -61,7 +57,7 @@ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 }; #if HAVE_GETTEXT -#include <locale.h> /* for setlocale */ +# include <locale.h> /* for setlocale */ #endif /* HAVE_GETTEXT */ #if ! HAVE_LOCALTIME_RZ @@ -77,7 +73,7 @@ extern int optind; #endif /* The minimum and maximum finite time values. */ -enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 }; +enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 }; static time_t const absolute_min_time = ((time_t) -1 < 0 ? (- ((time_t) ~ (time_t) 0 < 0) @@ -88,22 +84,27 @@ static time_t const absolute_max_time = ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)) : -1); static int longest; -static char * progname; +static char const *progname; static bool warned; static bool errout; static char const *abbr(struct tm const *); -static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE; +ATTRIBUTE_REPRODUCIBLE static intmax_t delta(struct tm *, struct tm *); static void dumptime(struct tm const *); -static time_t hunt(timezone_t, char *, time_t, time_t); +static time_t hunt(timezone_t, time_t, time_t, bool); static void show(timezone_t, char *, time_t, bool); +static void showextrema(timezone_t, char *, time_t, struct tm *, time_t); 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; +ATTRIBUTE_REPRODUCIBLE static time_t yeartot(intmax_t); -/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */ -#define is_digit(c) ((unsigned)(c) - '0' <= 9) +/* Is C an ASCII digit? */ +static bool +is_digit(char c) +{ + return '0' <= c && c <= '9'; +} /* Is A an alphabetic character in the C locale? */ static bool @@ -124,26 +125,49 @@ is_alpha(char a) } } -/* Return A + B, exiting if the result would overflow. */ -static size_t -sumsize(size_t a, size_t b) +ATTRIBUTE_NORETURN static void +size_overflow(void) { - size_t sum = a + b; - if (sum < a) { - fprintf(stderr, "%s: size overflow\n", progname); - exit(EXIT_FAILURE); - } - return sum; + fprintf(stderr, _("%s: size overflow\n"), progname); + exit(EXIT_FAILURE); +} + +/* Return A + B, exiting if the result would overflow either ptrdiff_t + or size_t. A and B are both nonnegative. */ +ATTRIBUTE_REPRODUCIBLE static ptrdiff_t +sumsize(ptrdiff_t a, ptrdiff_t b) +{ +#ifdef ckd_add + ptrdiff_t sum; + if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX) + return sum; +#else + if (a <= INDEX_MAX && b <= INDEX_MAX - a) + return a + b; +#endif + size_overflow(); +} + +/* Return the size of of the string STR, including its trailing NUL. + Report an error and exit if this would exceed INDEX_MAX which means + pointer subtraction wouldn't work. */ +static ptrdiff_t +xstrsize(char const *str) +{ + size_t len = strlen(str); + if (len < INDEX_MAX) + return len + 1; + size_overflow(); } /* Return a pointer to a newly allocated buffer of size SIZE, exiting - on failure. SIZE should be nonzero. */ -static void * ATTRIBUTE_MALLOC -xmalloc(size_t size) + on failure. SIZE should be positive. */ +ATTRIBUTE_MALLOC static void * +xmalloc(ptrdiff_t size) { void *p = malloc(size); if (!p) { - perror(progname); + fprintf(stderr, _("%s: Memory exhausted\n"), progname); exit(EXIT_FAILURE); } return p; @@ -204,7 +228,7 @@ localtime_r(time_t *tp, struct tm *tmp) # undef localtime_rz # define localtime_rz zdump_localtime_rz static struct tm * -localtime_rz(timezone_t rz, time_t *tp, struct tm *tmp) +localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz, time_t *tp, struct tm *tmp) { return localtime_r(tp, tmp); } @@ -227,33 +251,65 @@ mktime_z(timezone_t tz, struct tm *tmp) static timezone_t tzalloc(char const *val) { +# if HAVE_SETENV + if (setenv("TZ", val, 1) != 0) { + char const *e = strerror(errno); + fprintf(stderr, _("%s: setenv: %s\n"), progname, e); + exit(EXIT_FAILURE); + } + tzset(); + return &optarg; /* Any valid non-null char ** will do. */ +# else + enum { TZeqlen = 3 }; + static char const TZeq[TZeqlen] = "TZ="; static char **fakeenv; - char **env = fakeenv; - char *env0; - if (! env) { - char **e = environ; - int to; - - while (*e++) - continue; - env = xmalloc(sumsize(sizeof *environ, - (e - environ) * sizeof *environ)); - to = 1; - for (e = environ; (env[to] = *e); e++) - to += strncmp(*e, "TZ=", 3) != 0; + static ptrdiff_t fakeenv0size; + void *freeable = NULL; + char **env = fakeenv, **initial_environ; + ptrdiff_t valsize = xstrsize(val); + if (fakeenv0size < valsize) { + char **e = environ, **to; + ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */ + + while (*e++) { +# ifdef ckd_add + if (ckd_add(&initial_nenvptrs, initial_nenvptrs, 1) + || INDEX_MAX < initial_nenvptrs) + size_overflow(); +# else + if (initial_nenvptrs == INDEX_MAX / sizeof *environ) + size_overflow(); + initial_nenvptrs++; +# endif + } + fakeenv0size = sumsize(valsize, valsize); + fakeenv0size = max(fakeenv0size, 64); + freeable = env; + fakeenv = env = + xmalloc(sumsize(sumsize(sizeof *environ, + initial_nenvptrs * sizeof *environ), + sumsize(TZeqlen, fakeenv0size))); + to = env + 1; + for (e = environ; (*to = *e); e++) + to += strncmp(*e, TZeq, TZeqlen) != 0; + env[0] = memcpy(to + 1, TZeq, TZeqlen); } - env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val))); - env[0] = strcat(strcpy(env0, "TZ="), val); - environ = fakeenv = env; + memcpy(env[0] + TZeqlen, val, valsize); + initial_environ = environ; + environ = env; tzset(); - return env; + free(freeable); + return initial_environ; +# endif } static void -tzfree(timezone_t env) +tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ) { - environ = env + 1; - free(env[0]); +# if !HAVE_SETENV + environ = initial_environ; + tzset(); +# endif } #endif /* ! USE_LOCALTIME_RZ */ @@ -263,11 +319,25 @@ static void gmtzinit(void) { if (USE_LOCALTIME_RZ) { - static char const utc[] = "UTC0"; - gmtz = tzalloc(utc); + /* Try "GMT" first to find out whether this is one of the rare + platforms where time_t counts leap seconds; this works due to + the "Zone GMT 0 - GMT" line in the "etcetera" file. If "GMT" + fails, fall back on "GMT0" which might be similar due to the + "Link GMT GMT0" line in the "backward" file, and which + should work on all POSIX platforms. The rest of zdump does not + use the "GMT" abbreviation that comes from this setting, so it + is OK to use "GMT" here rather than the modern "UTC" which + would not work on platforms that omit the "backward" file. */ + gmtz = tzalloc("GMT"); if (!gmtz) { - perror(utc); - exit(EXIT_FAILURE); + static char const gmt0[] = "GMT0"; + gmtz = tzalloc(gmt0); + if (!gmtz) { + char const *e = strerror(errno); + fprintf(stderr, _("%s: unknown timezone '%s': %s\n"), + progname, gmt0, e); + exit(EXIT_FAILURE); + } } } } @@ -345,23 +415,23 @@ abbrok(const char *const abbrp, const char *const zone) /* Return a time zone abbreviation. If the abbreviation needs to be saved, use *BUF (of size *BUFALLOC) to save it, and return the - abbreviation in the possibly-reallocated *BUF. Otherwise, just + abbreviation in the possibly reallocated *BUF. Otherwise, just return the abbreviation. Get the abbreviation from TMP. Exit on memory allocation failure. */ static char const * -saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp) +saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp) { char const *ab = abbr(tmp); if (HAVE_LOCALTIME_RZ) return ab; else { - size_t ablen = strlen(ab); - if (*bufalloc <= ablen) { + ptrdiff_t absize = xstrsize(ab); + if (*bufalloc < absize) { free(*buf); /* Make the new buffer at least twice as long as the old, to avoid O(N**2) behavior on repeated calls. */ - *bufalloc = sumsize(*bufalloc, ablen + 1); + *bufalloc = sumsize(*bufalloc, absize); *buf = xmalloc(*bufalloc); } @@ -406,7 +476,7 @@ main(int argc, char *argv[]) { /* These are static so that they're initially zero. */ static char * abbrev; - static size_t abbrevsize; + static ptrdiff_t abbrevsize; register int i; register bool vflag; @@ -422,12 +492,12 @@ main(int argc, char *argv[]) cuthitime = absolute_max_time; #if HAVE_GETTEXT setlocale(LC_ALL, ""); -#ifdef TZ_DOMAINDIR +# ifdef TZ_DOMAINDIR bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR); -#endif /* defined TEXTDOMAINDIR */ +# endif /* defined TEXTDOMAINDIR */ textdomain(TZ_DOMAIN); #endif /* HAVE_GETTEXT */ - progname = argv[0]; + progname = argv[0] ? argv[0] : "zdump"; for (i = 1; i < argc; ++i) if (strcmp(argv[i], "--version") == 0) { printf("zdump %s%s\n", PKGVERSION, TZVERSION); @@ -447,7 +517,7 @@ main(int argc, char *argv[]) case -1: if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) goto arg_processing_done; - /* Fall through. */ + ATTRIBUTE_FALLTHROUGH; default: usage(stderr, EXIT_FAILURE); } @@ -484,8 +554,8 @@ main(int argc, char *argv[]) if (cuttimes != loend && !*loend) { hi = lo; if (hi < cuthitime) { - if (hi < absolute_min_time) - hi = absolute_min_time; + if (hi < absolute_min_time + 1) + hi = absolute_min_time + 1; cuthitime = hi; } } else if (cuttimes != loend && *loend == ',' @@ -497,8 +567,8 @@ main(int argc, char *argv[]) cutlotime = lo; } if (hi < cuthitime) { - if (hi < absolute_min_time) - hi = absolute_min_time; + if (hi < absolute_min_time + 1) + hi = absolute_min_time + 1; cuthitime = hi; } } else { @@ -510,14 +580,17 @@ main(int argc, char *argv[]) } } gmtzinit(); - INITIALIZE (now); - if (! (iflag | vflag | Vflag)) + if (iflag | vflag | Vflag) + now = 0; + else { now = time(NULL); + now |= !now; + } longest = 0; for (i = optind; i < argc; i++) { size_t arglen = strlen(argv[i]); if (longest < arglen) - longest = arglen < INT_MAX ? arglen : INT_MAX; + longest = min(arglen, INT_MAX); } for (i = optind; i < argc; ++i) { @@ -527,10 +600,12 @@ main(int argc, char *argv[]) struct tm tm, newtm; bool tm_ok; if (!tz) { - perror(argv[i]); + char const *e = strerror(errno); + fprintf(stderr, _("%s: unknown timezone '%s': %s\n"), + progname, argv[i], e); return EXIT_FAILURE; } - if (! (iflag | vflag | Vflag)) { + if (now) { show(tz, argv[i], now, false); tzfree(tz); continue; @@ -539,12 +614,15 @@ main(int argc, char *argv[]) t = absolute_min_time; if (! (iflag | Vflag)) { show(tz, argv[i], t, true); - t += SECSPERDAY; - show(tz, argv[i], t, true); + if (my_localtime_rz(tz, &t, &tm) == NULL + && t < cutlotime) { + time_t newt = cutlotime; + if (my_localtime_rz(tz, &newt, &newtm) != NULL) + showextrema(tz, argv[i], t, NULL, newt); + } } - if (t < cutlotime) - t = cutlotime; - INITIALIZE (ab); + if (t + 1 < cutlotime) + t = cutlotime - 1; tm_ok = my_localtime_rz(tz, &t, &tm) != NULL; if (tm_ok) { ab = saveabbr(&abbrev, &abbrevsize, &tm); @@ -552,19 +630,20 @@ main(int argc, char *argv[]) showtrans("\nTZ=%f", &tm, t, ab, argv[i]); showtrans("-\t-\t%Q", &tm, t, ab, argv[i]); } - } - while (t < cuthitime) { + } else + ab = NULL; + while (t < cuthitime - 1) { time_t newt = ((t < absolute_max_time - SECSPERDAY / 2 - && t + SECSPERDAY / 2 < cuthitime) + && t + SECSPERDAY / 2 < cuthitime - 1) ? t + SECSPERDAY / 2 - : cuthitime); + : cuthitime - 1); struct tm *newtmp = localtime_rz(tz, &newt, &newtm); bool newtm_ok = newtmp != NULL; if (tm_ok != newtm_ok - || (tm_ok && (delta(&newtm, &tm) != newt - t - || newtm.tm_isdst != tm.tm_isdst - || strcmp(abbr(&newtm), ab) != 0))) { - newt = hunt(tz, argv[i], t, newt); + || (ab && (delta(&newtm, &tm) != newt - t + || newtm.tm_isdst != tm.tm_isdst + || strcmp(abbr(&newtm), ab) != 0))) { + newt = hunt(tz, t, newt, false); newtmp = localtime_rz(tz, &newt, &newtm); newtm_ok = newtmp != NULL; if (iflag) @@ -583,11 +662,15 @@ main(int argc, char *argv[]) } } if (! (iflag | Vflag)) { - t = absolute_max_time; - t -= SECSPERDAY; - show(tz, argv[i], t, true); - t += SECSPERDAY; - show(tz, argv[i], t, true); + time_t newt = absolute_max_time; + t = cuthitime; + if (t < newt) { + struct tm *tmp = my_localtime_rz(tz, &t, &tm); + if (tmp != NULL + && my_localtime_rz(tz, &newt, &newtm) == NULL) + showextrema(tz, argv[i], t, tmp, newt); + } + show(tz, argv[i], absolute_max_time, true); } tzfree(tz); } @@ -640,36 +723,42 @@ yeartot(intmax_t y) return t; } +/* Search for a discontinuity in timezone TZ, in the + timestamps ranging from LOT through HIT. LOT and HIT disagree + about some aspect of timezone. If ONLY_OK, search only for + definedness changes, i.e., localtime succeeds on one side of the + transition but fails on the other side. Return the timestamp just + before the transition from LOT's settings. */ + static time_t -hunt(timezone_t tz, char *name, time_t lot, time_t hit) +hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok) { static char * loab; - static size_t loabsize; - char const * ab; - time_t t; + static ptrdiff_t loabsize; struct tm lotm; struct tm tm; + + /* Convert LOT into a broken-down time here, even though our + caller already did that. On platforms without TM_ZONE, + tzname may have been altered since our caller broke down + LOT, and tzname needs to be changed back. */ bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL; bool tm_ok; + char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL; - if (lotm_ok) - ab = saveabbr(&loab, &loabsize, &lotm); for ( ; ; ) { - time_t diff = hit - lot; - if (diff < 2) + /* T = average of LOT and HIT, rounding down. + Avoid overflow. */ + int rem_sum = lot % 2 + hit % 2; + time_t t = (rem_sum == 2) - (rem_sum < 0) + lot / 2 + hit / 2; + if (t == lot) break; - t = lot; - t += diff / 2; - if (t <= lot) - ++t; - else if (t >= hit) - --t; 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) { + if (lotm_ok == tm_ok + && (only_ok + || (ab && tm.tm_isdst == lotm.tm_isdst + && delta(&tm, &lotm) == t - lot + && strcmp(abbr(&tm), ab) == 0))) { lot = t; if (tm_ok) lotm = tm; @@ -685,11 +774,11 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit) static intmax_t delta_nonneg(struct tm *newp, struct tm *oldp) { - register intmax_t result; - register int tmy; - - result = 0; - for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy) + intmax_t oldy = oldp->tm_year; + int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT; + intmax_t sec = SECSPERREPEAT, result = cycles * sec; + int tmy = oldp->tm_year + cycles * YEARSPERREPEAT; + for ( ; tmy < newp->tm_year; ++tmy) result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE); result += newp->tm_yday - oldp->tm_yday; result *= HOURSPERDAY; @@ -730,7 +819,8 @@ adjusted_yday(struct tm const *a, struct tm const *b) 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, time_t *t, struct tm const *b) +gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t, + ATTRIBUTE_MAYBE_UNUSED struct tm const *b) { #ifdef TM_GMTOFF return a->TM_GMTOFF; @@ -764,6 +854,7 @@ show(timezone_t tz, char *zone, time_t t, bool v) gmtmp = my_gmtime_r(&t, &gmtm); if (gmtmp == NULL) { printf(tformat(), t); + printf(_(" (gmtime failed)")); } else { dumptime(gmtmp); printf(" UT"); @@ -771,8 +862,11 @@ show(timezone_t tz, char *zone, time_t t, bool v) printf(" = "); } tmp = my_localtime_rz(tz, &t, &tm); - dumptime(tmp); - if (tmp != NULL) { + if (tmp == NULL) { + printf(tformat(), t); + printf(_(" (localtime failed)")); + } else { + dumptime(tmp); if (*abbr(tmp) != '\0') printf(" %s", abbr(tmp)); if (v) { @@ -787,13 +881,58 @@ show(timezone_t tz, char *zone, time_t t, bool v) abbrok(abbr(tmp), zone); } +/* Show timestamps just before and just after a transition between + defined and undefined (or vice versa) in either localtime or + gmtime. These transitions are for timezone TZ with name ZONE, in + the range from LO (with broken-down time LOTMP if that is nonnull) + through HI. LO and HI disagree on definedness. */ + +static void +showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi) +{ + struct tm localtm[2], gmtm[2]; + time_t t, boundary = hunt(tz, lo, hi, true); + bool old = false; + hi = (SECSPERDAY < hi - boundary + ? boundary + SECSPERDAY + : hi + (hi < TIME_T_MAX)); + if (SECSPERDAY < boundary - lo) { + lo = boundary - SECSPERDAY; + lotmp = my_localtime_rz(tz, &lo, &localtm[old]); + } + if (lotmp) + localtm[old] = *lotmp; + else + localtm[old].tm_sec = -1; + if (! my_gmtime_r(&lo, &gmtm[old])) + gmtm[old].tm_sec = -1; + + /* Search sequentially for definedness transitions. Although this + could be sped up by refining 'hunt' to search for either + localtime or gmtime definedness transitions, it hardly seems + worth the trouble. */ + for (t = lo + 1; t < hi; t++) { + bool new = !old; + if (! my_localtime_rz(tz, &t, &localtm[new])) + localtm[new].tm_sec = -1; + if (! my_gmtime_r(&t, &gmtm[new])) + gmtm[new].tm_sec = -1; + if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0)) + | ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) { + show(tz, zone, t - 1, true); + show(tz, zone, t, true); + } + old = new; + } +} + #if HAVE_SNPRINTF # define my_snprintf snprintf #else # include <stdarg.h> /* A substitute for snprintf that is good enough for zdump. */ -static int ATTRIBUTE_FORMAT((printf, 3, 4)) +ATTRIBUTE_FORMAT((printf, 3, 4)) static int my_snprintf(char *s, size_t size, char const *format, ...) { int n; @@ -831,7 +970,7 @@ my_snprintf(char *s, size_t size, char const *format, ...) 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) +format_local_time(char *buf, ptrdiff_t size, struct tm const *tm) { int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour; return (ss @@ -854,7 +993,7 @@ format_local_time(char *buf, size_t size, struct tm const *tm) 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) +format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t) { long off = gmtoff(tm, &t, NULL); char sign = ((off < 0 @@ -883,11 +1022,11 @@ format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t) 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) +static ptrdiff_t +format_quoted_string(char *buf, ptrdiff_t size, char const *p) { char *b = buf; - size_t s = size; + ptrdiff_t s = size; if (!s) return size; *b++ = '"', s--; @@ -925,11 +1064,11 @@ format_quoted_string(char *buf, size_t size, char const *p) and omit any trailing tabs. */ static bool -istrftime(char *buf, size_t size, char const *time_fmt, +istrftime(char *buf, ptrdiff_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; + ptrdiff_t s = size; char const *f = time_fmt, *p; for (p = f; ; p++) @@ -938,9 +1077,9 @@ istrftime(char *buf, size_t size, char const *time_fmt, 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; + ptrdiff_t formatted_len; + ptrdiff_t f_prefix_len = p - f; + ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2); char fbuf[100]; bool oversized = sizeof fbuf <= f_prefix_copy_size; char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf; @@ -972,7 +1111,7 @@ istrftime(char *buf, size_t size, char const *time_fmt, b += offlen, s -= offlen; if (show_abbr) { char const *abp; - size_t len; + ptrdiff_t len; if (s <= 1) return false; *b++ = '\t', s--; @@ -1011,7 +1150,7 @@ showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab, putchar('\n'); } else { char stackbuf[1000]; - size_t size = sizeof stackbuf; + ptrdiff_t size = sizeof stackbuf; char *buf = stackbuf; char *bufalloc = NULL; while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) { @@ -1040,28 +1179,45 @@ abbr(struct tm const *tmp) /* ** The code below can fail on certain theoretical systems; -** it works on all known real-world systems as of 2004-12-30. +** it works on all known real-world systems as of 2022-01-25. */ static const char * tformat(void) { +#if HAVE__GENERIC + /* C11-style _Generic is more likely to return the correct + format when distinct types have the same size. */ + char const *fmt = + _Generic(+ (time_t) 0, + int: "%d", long: "%ld", long long: "%lld", + unsigned: "%u", unsigned long: "%lu", + unsigned long long: "%llu", + default: NULL); + if (fmt) + return fmt; + fmt = _Generic((time_t) 0, + intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX, + default: NULL); + if (fmt) + return fmt; +#endif if (0 > (time_t) -1) { /* signed */ - if (sizeof (time_t) == sizeof (intmax_t)) + if (sizeof(time_t) == sizeof(intmax_t)) return "%"PRIdMAX; - if (sizeof (time_t) > sizeof (long)) + if (sizeof(time_t) > sizeof(long)) return "%lld"; - if (sizeof (time_t) > sizeof (int)) + if (sizeof(time_t) > sizeof(int)) return "%ld"; return "%d"; } #ifdef PRIuMAX - if (sizeof (time_t) == sizeof (uintmax_t)) + if (sizeof(time_t) == sizeof(uintmax_t)) return "%"PRIuMAX; #endif - if (sizeof (time_t) > sizeof (unsigned long)) + if (sizeof(time_t) > sizeof(unsigned long)) return "%llu"; - if (sizeof (time_t) > sizeof (unsigned int)) + if (sizeof(time_t) > sizeof(unsigned int)) return "%lu"; return "%u"; } @@ -1076,33 +1232,24 @@ dumptime(register const struct tm *timeptr) "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - register const char * wn; - register const char * mn; register int lead; register int trail; + int DIVISOR = 10; - if (timeptr == NULL) { - printf("NULL"); - return; - } /* ** The packaged localtime_rz and gmtime_r never put out-of-range ** values in tm_wday or tm_mon, but since this code might be compiled ** with other (perhaps experimental) versions, paranoia is in order. */ - if (timeptr->tm_wday < 0 || timeptr->tm_wday >= - (int) (sizeof wday_name / sizeof wday_name[0])) - wn = "???"; - else wn = wday_name[timeptr->tm_wday]; - if (timeptr->tm_mon < 0 || timeptr->tm_mon >= - (int) (sizeof mon_name / sizeof mon_name[0])) - mn = "???"; - else mn = mon_name[timeptr->tm_mon]; printf("%s %s%3d %.2d:%.2d:%.2d ", - wn, mn, + ((0 <= timeptr->tm_wday + && timeptr->tm_wday < sizeof wday_name / sizeof wday_name[0]) + ? wday_name[timeptr->tm_wday] : "???"), + ((0 <= timeptr->tm_mon + && timeptr->tm_mon < sizeof mon_name / sizeof mon_name[0]) + ? mon_name[timeptr->tm_mon] : "???"), timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); -#define DIVISOR 10 trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR; lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR + trail / DIVISOR; |