aboutsummaryrefslogtreecommitdiff
path: root/timezone/zdump.c
diff options
context:
space:
mode:
authorJoseph Myers <joseph@codesourcery.com>2017-06-16 11:09:21 +0000
committerJoseph Myers <joseph@codesourcery.com>2017-06-16 11:09:21 +0000
commit92bd70fb85bce57ac47ba5d8af008736832c955a (patch)
tree3fec830fb36ef1af2aaeb411f23dc76c3fcd7c15 /timezone/zdump.c
parenta448ee41e70a0b1d26557ffce8e550fe4aad2525 (diff)
downloadglibc-92bd70fb85bce57ac47ba5d8af008736832c955a.tar
glibc-92bd70fb85bce57ac47ba5d8af008736832c955a.tar.gz
glibc-92bd70fb85bce57ac47ba5d8af008736832c955a.tar.bz2
glibc-92bd70fb85bce57ac47ba5d8af008736832c955a.zip
Update timezone code from tzcode 2017b.
This patch updates files coming from tzcode to the versions in tzcode 2017b. A couple of changes to other glibc code are needed. time/tzset.c was using the SECSPERDAY macro from tzfile.h, which no longer defines that macro, so a local definition is added to tzset.c. Because timezone/private.h now defines the _ macro whenever HAVE_GETTEXT is true, even if it was previously defined, it is also necessary to avoid a conflict with the definition in include/libintl.h. Defining _ISOMAC is the obvious way to avoid such internal definitions being visible, together with defining TZ_DOMAIN so that zic and zdump continue to get the messages from the libc domain as desired. However, zic and zdump rely on PKGVERSION and REPORT_BUGS_TO from config.h, which is not included by default with _ISOMAC, so -include config.h needs adding to the options for these programs as well. Together those changes allow unmodified tzcode 2017b sources to work in glibc. Tested for x86_64. * timezone/private.h: Update from tzcode 2017b. * timezone/tzfile.h: Likewise. * timezone/tzselect.ksh: Likewise. * timezone/zdump.c: Likewise. * timezone/zic.c: Likewise. * timezone/Makefile (tz-cflags): Add -D_ISOMAC -DTZ_DOMAIN='"libc"' -include $(common-objpfx)config.h. * time/tzset.c (SECSPERDAY): New macro.
Diffstat (limited to 'timezone/zdump.c')
-rw-r--r--timezone/zdump.c541
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)
{