Home | History | Annotate | Line # | Download | only in time
      1  1.65  christos /*	$NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $	*/
      2  1.50  christos /* Dump time zone data in a textual format.  */
      3  1.50  christos 
      4  1.17   mlelstv /*
      5  1.17   mlelstv ** This file is in the public domain, so clarified as of
      6  1.17   mlelstv ** 2009-05-17 by Arthur David Olson.
      7  1.17   mlelstv */
      8   1.2       jtc 
      9   1.6  christos #include <sys/cdefs.h>
     10   1.1       jtc #ifndef lint
     11  1.65  christos __RCSID("$NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $");
     12   1.1       jtc #endif /* !defined lint */
     13   1.1       jtc 
     14  1.36  christos #ifndef NETBSD_INSPIRED
     15  1.36  christos # define NETBSD_INSPIRED 1
     16  1.36  christos #endif
     17  1.36  christos 
     18  1.46  christos #include <err.h>
     19  1.29  christos #include "private.h"
     20  1.47  christos #include <stdio.h>
     21  1.47  christos 
     22  1.36  christos #ifndef HAVE_LOCALTIME_R
     23  1.36  christos # define HAVE_LOCALTIME_R 1
     24  1.36  christos #endif
     25  1.36  christos 
     26  1.36  christos #ifndef HAVE_LOCALTIME_RZ
     27  1.36  christos # ifdef TM_ZONE
     28  1.36  christos #  define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
     29  1.36  christos # else
     30  1.36  christos #  define HAVE_LOCALTIME_RZ 0
     31  1.36  christos # endif
     32  1.36  christos #endif
     33  1.36  christos 
     34  1.36  christos #ifndef HAVE_TZSET
     35  1.36  christos # define HAVE_TZSET 1
     36  1.36  christos #endif
     37  1.27  christos 
     38  1.17   mlelstv #ifndef ZDUMP_LO_YEAR
     39  1.57  christos # define ZDUMP_LO_YEAR (-500)
     40  1.17   mlelstv #endif /* !defined ZDUMP_LO_YEAR */
     41  1.17   mlelstv 
     42  1.17   mlelstv #ifndef ZDUMP_HI_YEAR
     43  1.57  christos # define ZDUMP_HI_YEAR 2500
     44  1.17   mlelstv #endif /* !defined ZDUMP_HI_YEAR */
     45   1.1       jtc 
     46  1.17   mlelstv #define SECSPERNYEAR	(SECSPERDAY * DAYSPERNYEAR)
     47  1.17   mlelstv #define SECSPERLYEAR	(SECSPERNYEAR + SECSPERDAY)
     48  1.31  christos #define SECSPER400YEARS	(SECSPERNYEAR * (intmax_t) (300 + 3)	\
     49  1.31  christos 			 + SECSPERLYEAR * (intmax_t) (100 - 3))
     50  1.31  christos 
     51  1.31  christos /*
     52  1.31  christos ** True if SECSPER400YEARS is known to be representable as an
     53  1.31  christos ** intmax_t.  It's OK that SECSPER400YEARS_FITS can in theory be false
     54  1.31  christos ** even if SECSPER400YEARS is representable, because when that happens
     55  1.31  christos ** the code merely runs a bit more slowly, and this slowness doesn't
     56  1.31  christos ** occur on any practical platform.
     57  1.31  christos */
     58  1.31  christos enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
     59  1.17   mlelstv 
     60  1.17   mlelstv #if HAVE_GETTEXT
     61  1.57  christos # include <locale.h> /* for setlocale */
     62  1.17   mlelstv #endif /* HAVE_GETTEXT */
     63   1.3       jtc 
     64  1.36  christos #if ! HAVE_LOCALTIME_RZ
     65  1.36  christos # undef  timezone_t
     66  1.36  christos # define timezone_t char **
     67  1.36  christos #endif
     68  1.36  christos 
     69  1.43  christos #if !HAVE_POSIX_DECLS
     70  1.17   mlelstv extern int	getopt(int argc, char * const argv[],
     71  1.17   mlelstv 			const char * options);
     72   1.1       jtc extern char *	optarg;
     73   1.1       jtc extern int	optind;
     74  1.43  christos #endif
     75   1.1       jtc 
     76  1.29  christos /* The minimum and maximum finite time values.  */
     77  1.54  christos enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
     78  1.29  christos static time_t	absolute_min_time =
     79  1.31  christos   ((time_t) -1 < 0
     80  1.41  christos     ? (- ((time_t) ~ (time_t) 0 < 0)
     81  1.41  christos        - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
     82  1.29  christos     : 0);
     83  1.29  christos static time_t	absolute_max_time =
     84  1.31  christos   ((time_t) -1 < 0
     85  1.41  christos     ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
     86  1.29  christos    : -1);
     87   1.5       jtc static size_t	longest;
     88  1.59  christos static char const *progname;
     89  1.36  christos static bool	warned;
     90  1.36  christos static bool	errout;
     91  1.17   mlelstv 
     92  1.36  christos static char const *abbr(struct tm const *);
     93  1.64  christos static intmax_t delta(struct tm *, struct tm *);
     94  1.36  christos static void dumptime(struct tm const *);
     95  1.59  christos static time_t hunt(timezone_t, time_t, time_t, bool);
     96  1.36  christos static void show(timezone_t, char *, time_t, bool);
     97  1.54  christos static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
     98  1.44  christos static void showtrans(char const *, struct tm const *, time_t, char const *,
     99  1.44  christos 		      char const *);
    100  1.36  christos static const char *tformat(void);
    101  1.64  christos ATTRIBUTE_PURE_114833 static time_t yeartot(intmax_t);
    102  1.17   mlelstv 
    103  1.54  christos /* Is C an ASCII digit?  */
    104  1.54  christos static bool
    105  1.54  christos is_digit(char c)
    106  1.54  christos {
    107  1.54  christos   return '0' <= c && c <= '9';
    108  1.54  christos }
    109  1.42  christos 
    110  1.34  christos /* Is A an alphabetic character in the C locale?  */
    111  1.36  christos static bool
    112  1.34  christos is_alpha(char a)
    113  1.34  christos {
    114  1.34  christos 	switch (a) {
    115  1.34  christos 	  default:
    116  1.36  christos 		return false;
    117  1.34  christos 	  case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
    118  1.34  christos 	  case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
    119  1.34  christos 	  case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
    120  1.34  christos 	  case 'V': case 'W': case 'X': case 'Y': case 'Z':
    121  1.34  christos 	  case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
    122  1.34  christos 	  case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
    123  1.34  christos 	  case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
    124  1.34  christos 	  case 'v': case 'w': case 'x': case 'y': case 'z':
    125  1.36  christos 	  	return true;
    126  1.34  christos 	}
    127  1.34  christos }
    128  1.34  christos 
    129  1.61  christos ATTRIBUTE_NORETURN static void
    130  1.59  christos size_overflow(void)
    131  1.59  christos {
    132  1.59  christos   fprintf(stderr, _("%s: size overflow\n"), progname);
    133  1.59  christos   exit(EXIT_FAILURE);
    134  1.59  christos }
    135  1.59  christos 
    136  1.59  christos /* Return A + B, exiting if the result would overflow either ptrdiff_t
    137  1.62  christos    or size_t.  A and B are both nonnegative.  */
    138  1.64  christos ATTRIBUTE_PURE_114833 static ptrdiff_t
    139  1.36  christos sumsize(size_t a, size_t b)
    140  1.36  christos {
    141  1.59  christos #ifdef ckd_add
    142  1.59  christos   ptrdiff_t sum;
    143  1.62  christos   if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
    144  1.59  christos     return sum;
    145  1.59  christos #else
    146  1.62  christos   if (a <= INDEX_MAX && b <= INDEX_MAX - a)
    147  1.59  christos     return a + b;
    148  1.59  christos #endif
    149  1.59  christos   size_overflow();
    150  1.36  christos }
    151  1.36  christos 
    152  1.62  christos 
    153  1.44  christos /* Return a pointer to a newly allocated buffer of size SIZE, exiting
    154  1.62  christos    on failure.  SIZE should be positive.  */
    155  1.64  christos static void *
    156  1.62  christos xmalloc(ptrdiff_t size)
    157  1.44  christos {
    158  1.44  christos   void *p = malloc(size);
    159  1.44  christos   if (!p) {
    160  1.54  christos     fprintf(stderr, _("%s: Memory exhausted\n"), progname);
    161  1.44  christos     exit(EXIT_FAILURE);
    162  1.44  christos   }
    163  1.44  christos   return p;
    164  1.44  christos }
    165  1.44  christos 
    166  1.36  christos #if ! HAVE_TZSET
    167  1.36  christos # undef tzset
    168  1.36  christos # define tzset zdump_tzset
    169  1.36  christos static void tzset(void) { }
    170  1.36  christos #endif
    171  1.36  christos 
    172  1.36  christos /* Assume gmtime_r works if localtime_r does.
    173  1.36  christos    A replacement localtime_r is defined below if needed.  */
    174  1.36  christos #if ! HAVE_LOCALTIME_R
    175  1.36  christos 
    176  1.36  christos # undef gmtime_r
    177  1.36  christos # define gmtime_r zdump_gmtime_r
    178  1.36  christos 
    179  1.36  christos static struct tm *
    180  1.36  christos gmtime_r(time_t *tp, struct tm *tmp)
    181  1.36  christos {
    182  1.36  christos 	struct tm *r = gmtime(tp);
    183  1.36  christos 	if (r) {
    184  1.36  christos 		*tmp = *r;
    185  1.36  christos 		r = tmp;
    186  1.36  christos 	}
    187  1.36  christos 	return r;
    188  1.36  christos }
    189  1.36  christos 
    190  1.36  christos #endif
    191  1.36  christos 
    192  1.36  christos /* Platforms with TM_ZONE don't need tzname, so they can use the
    193  1.36  christos    faster localtime_rz or localtime_r if available.  */
    194  1.36  christos 
    195  1.36  christos #if defined TM_ZONE && HAVE_LOCALTIME_RZ
    196  1.36  christos # define USE_LOCALTIME_RZ true
    197  1.36  christos #else
    198  1.36  christos # define USE_LOCALTIME_RZ false
    199  1.36  christos #endif
    200  1.36  christos 
    201  1.36  christos #if ! USE_LOCALTIME_RZ
    202  1.36  christos 
    203  1.36  christos # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
    204  1.36  christos #  undef localtime_r
    205  1.36  christos #  define localtime_r zdump_localtime_r
    206  1.36  christos static struct tm *
    207  1.36  christos localtime_r(time_t *tp, struct tm *tmp)
    208  1.36  christos {
    209  1.36  christos 	struct tm *r = localtime(tp);
    210  1.36  christos 	if (r) {
    211  1.36  christos 		*tmp = *r;
    212  1.36  christos 		r = tmp;
    213  1.36  christos 	}
    214  1.36  christos 	return r;
    215  1.36  christos }
    216  1.36  christos # endif
    217  1.36  christos 
    218  1.36  christos # undef localtime_rz
    219  1.36  christos # define localtime_rz zdump_localtime_rz
    220  1.36  christos static struct tm *
    221  1.62  christos localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz, time_t *tp, struct tm *tmp)
    222  1.36  christos {
    223  1.36  christos 	return localtime_r(tp, tmp);
    224  1.36  christos }
    225  1.36  christos 
    226  1.36  christos # ifdef TYPECHECK
    227  1.36  christos #  undef mktime_z
    228  1.36  christos #  define mktime_z zdump_mktime_z
    229  1.36  christos static time_t
    230  1.36  christos mktime_z(timezone_t tz, struct tm *tmp)
    231  1.36  christos {
    232  1.36  christos 	return mktime(tmp);
    233  1.36  christos }
    234  1.36  christos # endif
    235  1.36  christos 
    236  1.36  christos # undef tzalloc
    237  1.36  christos # undef tzfree
    238  1.36  christos # define tzalloc zdump_tzalloc
    239  1.36  christos # define tzfree zdump_tzfree
    240  1.36  christos 
    241  1.36  christos static timezone_t
    242  1.36  christos tzalloc(char const *val)
    243  1.36  christos {
    244  1.58  christos # if HAVE_SETENV
    245  1.58  christos   if (setenv("TZ", val, 1) != 0) {
    246  1.62  christos     char const *e = strerror(errno);
    247  1.62  christos     fprintf(stderr, _("%s: setenv: %s\n"), progname, e);
    248  1.58  christos     exit(EXIT_FAILURE);
    249  1.58  christos   }
    250  1.58  christos   tzset();
    251  1.59  christos   return &optarg;  /* Any valid non-null char ** will do.  */
    252  1.58  christos # else
    253  1.58  christos   enum { TZeqlen = 3 };
    254  1.58  christos   static char const TZeq[TZeqlen] = "TZ=";
    255  1.59  christos   static ptrdiff_t fakeenv0size;
    256  1.58  christos   void *freeable = NULL;
    257  1.58  christos   char **env = fakeenv, **initial_environ;
    258  1.65  christos   ptrdiff_t valsize = strlen(val) + 1;
    259  1.58  christos   if (fakeenv0size < valsize) {
    260  1.58  christos     char **e = environ, **to;
    261  1.59  christos     ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
    262  1.58  christos 
    263  1.59  christos     while (*e++) {
    264  1.59  christos #  ifdef ckd_add
    265  1.62  christos       if (ckd_add(&initial_nenvptrs, initial_nenvptrs, 1)
    266  1.62  christos 	  || INDEX_MAX < initial_nenvptrs)
    267  1.59  christos 	size_overflow();
    268  1.59  christos #  else
    269  1.65  christos       if (initial_nenvptrs == INDEX_MAX / sizeof *environ)
    270  1.59  christos 	size_overflow();
    271  1.59  christos       initial_nenvptrs++;
    272  1.59  christos #  endif
    273  1.58  christos     fakeenv0size = sumsize(valsize, valsize);
    274  1.58  christos     fakeenv0size = max(fakeenv0size, 64);
    275  1.58  christos     freeable = env;
    276  1.58  christos     fakeenv = env =
    277  1.58  christos       xmalloc(sumsize(sumsize(sizeof *environ,
    278  1.58  christos 			      initial_nenvptrs * sizeof *environ),
    279  1.58  christos 		      sumsize(TZeqlen, fakeenv0size)));
    280  1.58  christos     to = env + 1;
    281  1.58  christos     for (e = environ; (*to = *e); e++)
    282  1.58  christos       to += strncmp(*e, TZeq, TZeqlen) != 0;
    283  1.58  christos     env[0] = memcpy(to + 1, TZeq, TZeqlen);
    284  1.58  christos   }
    285  1.58  christos   memcpy(env[0] + TZeqlen, val, valsize);
    286  1.58  christos   initial_environ = environ;
    287  1.58  christos   environ = env;
    288  1.58  christos   tzset();
    289  1.58  christos   free(freeable);
    290  1.58  christos   return initial_environ;
    291  1.58  christos # endif
    292  1.36  christos }
    293  1.36  christos 
    294  1.36  christos static void
    295  1.62  christos tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ)
    296  1.36  christos {
    297  1.58  christos # if !HAVE_SETENV
    298  1.58  christos   environ = initial_environ;
    299  1.58  christos   tzset();
    300  1.58  christos # endif
    301  1.36  christos }
    302  1.36  christos #endif /* ! USE_LOCALTIME_RZ */
    303  1.36  christos 
    304  1.48  christos /* A UT time zone, and its initializer.  */
    305  1.36  christos static timezone_t gmtz;
    306  1.36  christos static void
    307  1.36  christos gmtzinit(void)
    308  1.36  christos {
    309  1.54  christos   if (USE_LOCALTIME_RZ) {
    310  1.54  christos     /* Try "GMT" first to find out whether this is one of the rare
    311  1.54  christos        platforms where time_t counts leap seconds; this works due to
    312  1.58  christos        the "Zone GMT 0 - GMT" line in the "etcetera" file.  If "GMT"
    313  1.54  christos        fails, fall back on "GMT0" which might be similar due to the
    314  1.58  christos        "Link GMT GMT0" line in the "backward" file, and which
    315  1.54  christos        should work on all POSIX platforms.  The rest of zdump does not
    316  1.54  christos        use the "GMT" abbreviation that comes from this setting, so it
    317  1.62  christos        is OK to use "GMT" here rather than the modern "UTC" which
    318  1.54  christos        would not work on platforms that omit the "backward" file.  */
    319  1.54  christos     gmtz = tzalloc("GMT");
    320  1.54  christos     if (!gmtz) {
    321  1.54  christos       static char const gmt0[] = "GMT0";
    322  1.54  christos       gmtz = tzalloc(gmt0);
    323  1.54  christos       if (!gmtz) {
    324  1.54  christos 	err(EXIT_FAILURE, "Cannot create %s", gmt0);
    325  1.54  christos       }
    326  1.54  christos     }
    327  1.54  christos   }
    328  1.36  christos }
    329  1.36  christos 
    330  1.48  christos /* Convert *TP to UT, storing the broken-down time into *TMP.
    331  1.36  christos    Return TMP if successful, NULL otherwise.  This is like gmtime_r(TP, TMP),
    332  1.36  christos    except typically faster if USE_LOCALTIME_RZ.  */
    333  1.36  christos static struct tm *
    334  1.36  christos my_gmtime_r(time_t *tp, struct tm *tmp)
    335  1.36  christos {
    336  1.36  christos 	return USE_LOCALTIME_RZ ?
    337  1.36  christos 	    localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
    338  1.36  christos }
    339  1.36  christos 
    340  1.17   mlelstv #ifndef TYPECHECK
    341  1.36  christos #define my_localtime_rz	localtime_rz
    342  1.17   mlelstv #else /* !defined TYPECHECK */
    343  1.17   mlelstv static struct tm *
    344  1.36  christos my_localtime_rz(timezone_t tz, const time_t *tp, struct tm *tmp)
    345  1.17   mlelstv {
    346  1.36  christos 	tmp = localtime_rz(tz, tp, tmp);
    347  1.36  christos 	if (tmp) {
    348  1.17   mlelstv 		struct tm	tm;
    349  1.26  christos 		time_t	t;
    350  1.17   mlelstv 
    351  1.17   mlelstv 		tm = *tmp;
    352  1.36  christos 		t = mktime_z(tz, &tm);
    353  1.31  christos 		if (t != *tp) {
    354  1.17   mlelstv 			(void) fflush(stdout);
    355  1.17   mlelstv 			(void) fprintf(stderr, "\n%s: ", progname);
    356  1.17   mlelstv 			(void) fprintf(stderr, tformat(), *tp);
    357  1.17   mlelstv 			(void) fprintf(stderr, " ->");
    358  1.17   mlelstv 			(void) fprintf(stderr, " year=%d", tmp->tm_year);
    359  1.17   mlelstv 			(void) fprintf(stderr, " mon=%d", tmp->tm_mon);
    360  1.17   mlelstv 			(void) fprintf(stderr, " mday=%d", tmp->tm_mday);
    361  1.17   mlelstv 			(void) fprintf(stderr, " hour=%d", tmp->tm_hour);
    362  1.17   mlelstv 			(void) fprintf(stderr, " min=%d", tmp->tm_min);
    363  1.17   mlelstv 			(void) fprintf(stderr, " sec=%d", tmp->tm_sec);
    364  1.17   mlelstv 			(void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
    365  1.17   mlelstv 			(void) fprintf(stderr, " -> ");
    366  1.17   mlelstv 			(void) fprintf(stderr, tformat(), t);
    367  1.17   mlelstv 			(void) fprintf(stderr, "\n");
    368  1.36  christos 			errout = true;
    369  1.17   mlelstv 		}
    370  1.17   mlelstv 	}
    371  1.17   mlelstv 	return tmp;
    372  1.17   mlelstv }
    373  1.17   mlelstv #endif /* !defined TYPECHECK */
    374  1.17   mlelstv 
    375  1.17   mlelstv static void
    376  1.26  christos abbrok(const char *const abbrp, const char *const zone)
    377  1.17   mlelstv {
    378  1.26  christos 	const char *cp;
    379  1.26  christos 	const char *wp;
    380  1.17   mlelstv 
    381  1.17   mlelstv 	if (warned)
    382  1.17   mlelstv 		return;
    383  1.17   mlelstv 	cp = abbrp;
    384  1.42  christos 	while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
    385  1.17   mlelstv 		++cp;
    386  1.53  christos 	if (*cp)
    387  1.53  christos 		wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
    388  1.53  christos 	else if (cp - abbrp < 3)
    389  1.42  christos 		wp = _("has fewer than 3 characters");
    390  1.17   mlelstv 	else if (cp - abbrp > 6)
    391  1.42  christos 		wp = _("has more than 6 characters");
    392  1.42  christos 	else
    393  1.17   mlelstv 		return;
    394  1.17   mlelstv 	(void) fflush(stdout);
    395  1.17   mlelstv 	(void) fprintf(stderr,
    396  1.17   mlelstv 		_("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
    397  1.17   mlelstv 		progname, zone, abbrp, wp);
    398  1.36  christos 	warned = errout = true;
    399  1.36  christos }
    400  1.36  christos 
    401  1.36  christos /* Return a time zone abbreviation.  If the abbreviation needs to be
    402  1.36  christos    saved, use *BUF (of size *BUFALLOC) to save it, and return the
    403  1.62  christos    abbreviation in the possibly reallocated *BUF.  Otherwise, just
    404  1.36  christos    return the abbreviation.  Get the abbreviation from TMP.
    405  1.36  christos    Exit on memory allocation failure.  */
    406  1.36  christos static char const *
    407  1.59  christos saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
    408  1.36  christos {
    409  1.36  christos 	char const *ab = abbr(tmp);
    410  1.36  christos 	if (HAVE_LOCALTIME_RZ)
    411  1.36  christos 		return ab;
    412  1.36  christos 	else {
    413  1.65  christos 		ptrdiff_t absize = strlen(ab) + 1;
    414  1.62  christos 		if (*bufalloc < absize) {
    415  1.36  christos 			free(*buf);
    416  1.36  christos 
    417  1.36  christos 			/* Make the new buffer at least twice as long as the
    418  1.36  christos 			   old, to avoid O(N**2) behavior on repeated calls.  */
    419  1.62  christos 			*bufalloc = sumsize(*bufalloc, absize);
    420  1.44  christos 			*buf = xmalloc(*bufalloc);
    421  1.36  christos 		}
    422  1.36  christos 		return strcpy(*buf, ab);
    423  1.36  christos 	}
    424  1.36  christos }
    425  1.36  christos 
    426  1.36  christos static void
    427  1.36  christos close_file(FILE *stream)
    428  1.36  christos {
    429  1.36  christos 	char const *e = (ferror(stream) ? _("I/O error")
    430  1.36  christos 	    : fclose(stream) != 0 ? strerror(errno) : NULL);
    431  1.36  christos 	if (e) {
    432  1.36  christos 		errx(EXIT_FAILURE, "%s", e);
    433  1.36  christos 	}
    434  1.17   mlelstv }
    435  1.17   mlelstv 
    436  1.24     joerg __dead static void
    437  1.26  christos usage(FILE *const stream, const int status)
    438  1.17   mlelstv {
    439  1.17   mlelstv 	(void) fprintf(stream,
    440  1.50  christos _("%s: usage: %s OPTIONS TIMEZONE ...\n"
    441  1.44  christos   "Options include:\n"
    442  1.44  christos   "  -c [L,]U   Start at year L (default -500), end before year U (default 2500)\n"
    443  1.44  christos   "  -t [L,]U   Start at time L, end before time U (in seconds since 1970)\n"
    444  1.44  christos   "  -i         List transitions briefly (format is experimental)\n" \
    445  1.44  christos   "  -v         List transitions verbosely\n"
    446  1.44  christos   "  -V         List transitions a bit less verbosely\n"
    447  1.44  christos   "  --help     Output this help\n"
    448  1.44  christos   "  --version  Output version info\n"
    449  1.29  christos   "\n"
    450  1.29  christos   "Report bugs to %s.\n"),
    451  1.44  christos 	   progname, progname, REPORT_BUGS_TO);
    452  1.36  christos 	if (status == EXIT_SUCCESS)
    453  1.36  christos 		close_file(stream);
    454  1.17   mlelstv 	exit(status);
    455  1.17   mlelstv }
    456   1.1       jtc 
    457   1.1       jtc int
    458  1.26  christos main(int argc, char *argv[])
    459  1.26  christos {
    460  1.36  christos 	/* These are static so that they're initially zero.  */
    461  1.36  christos 	static char *		abbrev;
    462  1.59  christos 	static ptrdiff_t	abbrevsize;
    463  1.36  christos 
    464  1.26  christos 	int		i;
    465  1.36  christos 	bool		vflag;
    466  1.36  christos 	bool		Vflag;
    467  1.26  christos 	char *		cutarg;
    468  1.29  christos 	char *		cuttimes;
    469  1.26  christos 	time_t		cutlotime;
    470  1.26  christos 	time_t		cuthitime;
    471  1.26  christos 	time_t		now;
    472  1.44  christos 	bool iflag = false;
    473  1.65  christos 	size_t arglenmax = 0;
    474   1.1       jtc 
    475  1.29  christos 	cutlotime = absolute_min_time;
    476  1.29  christos 	cuthitime = absolute_max_time;
    477  1.17   mlelstv #if HAVE_GETTEXT
    478  1.17   mlelstv 	(void) setlocale(LC_ALL, "");
    479  1.57  christos # ifdef TZ_DOMAINDIR
    480   1.3       jtc 	(void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
    481  1.57  christos # endif /* defined TEXTDOMAINDIR */
    482   1.3       jtc 	(void) textdomain(TZ_DOMAIN);
    483  1.17   mlelstv #endif /* HAVE_GETTEXT */
    484  1.59  christos 	progname = argv[0] ? argv[0] : "zdump";
    485  1.14    kleink 	for (i = 1; i < argc; ++i)
    486  1.14    kleink 		if (strcmp(argv[i], "--version") == 0) {
    487  1.28  christos 			(void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
    488  1.36  christos 			return EXIT_SUCCESS;
    489  1.17   mlelstv 		} else if (strcmp(argv[i], "--help") == 0) {
    490  1.22  christos 			usage(stdout, EXIT_SUCCESS);
    491  1.14    kleink 		}
    492  1.36  christos 	vflag = Vflag = false;
    493  1.29  christos 	cutarg = cuttimes = NULL;
    494  1.29  christos 	for (;;)
    495  1.44  christos 	  switch (getopt(argc, argv, "c:it:vV")) {
    496  1.29  christos 	  case 'c': cutarg = optarg; break;
    497  1.29  christos 	  case 't': cuttimes = optarg; break;
    498  1.44  christos 	  case 'i': iflag = true; break;
    499  1.36  christos 	  case 'v': vflag = true; break;
    500  1.36  christos 	  case 'V': Vflag = true; break;
    501  1.29  christos 	  case -1:
    502  1.29  christos 	    if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
    503  1.29  christos 	      goto arg_processing_done;
    504  1.59  christos 	    ATTRIBUTE_FALLTHROUGH;
    505  1.29  christos 	  default:
    506  1.29  christos 	    usage(stderr, EXIT_FAILURE);
    507  1.29  christos 	  }
    508  1.29  christos  arg_processing_done:;
    509  1.29  christos 
    510  1.44  christos 	if (iflag | vflag | Vflag) {
    511  1.29  christos 		intmax_t	lo;
    512  1.29  christos 		intmax_t	hi;
    513  1.32  christos 		char *loend, *hiend;
    514  1.30  christos 		intmax_t cutloyear = ZDUMP_LO_YEAR;
    515  1.30  christos 		intmax_t cuthiyear = ZDUMP_HI_YEAR;
    516  1.17   mlelstv 		if (cutarg != NULL) {
    517  1.32  christos 			lo = strtoimax(cutarg, &loend, 10);
    518  1.32  christos 			if (cutarg != loend && !*loend) {
    519  1.32  christos 				hi = lo;
    520  1.32  christos 				cuthiyear = hi;
    521  1.32  christos 			} else if (cutarg != loend && *loend == ','
    522  1.32  christos 				   && (hi = strtoimax(loend + 1, &hiend, 10),
    523  1.32  christos 				       loend + 1 != hiend && !*hiend)) {
    524  1.32  christos 				cutloyear = lo;
    525  1.17   mlelstv 				cuthiyear = hi;
    526  1.17   mlelstv 			} else {
    527  1.36  christos 				fprintf(stderr, _("%s: wild -c argument %s\n"),
    528  1.17   mlelstv 					progname, cutarg);
    529  1.36  christos 				return EXIT_FAILURE;
    530  1.17   mlelstv 			}
    531  1.17   mlelstv 		}
    532  1.29  christos 		if (cutarg != NULL || cuttimes == NULL) {
    533  1.29  christos 			cutlotime = yeartot(cutloyear);
    534  1.29  christos 			cuthitime = yeartot(cuthiyear);
    535  1.29  christos 		}
    536  1.29  christos 		if (cuttimes != NULL) {
    537  1.32  christos 			lo = strtoimax(cuttimes, &loend, 10);
    538  1.32  christos 			if (cuttimes != loend && !*loend) {
    539  1.32  christos 				hi = lo;
    540  1.29  christos 				if (hi < cuthitime) {
    541  1.54  christos 					if (hi < absolute_min_time + 1)
    542  1.54  christos 					  hi = absolute_min_time + 1;
    543  1.29  christos 					cuthitime = hi;
    544  1.29  christos 				}
    545  1.32  christos 			} else if (cuttimes != loend && *loend == ','
    546  1.32  christos 				   && (hi = strtoimax(loend + 1, &hiend, 10),
    547  1.32  christos 				       loend + 1 != hiend && !*hiend)) {
    548  1.29  christos 				if (cutlotime < lo) {
    549  1.29  christos 					if (absolute_max_time < lo)
    550  1.29  christos 						lo = absolute_max_time;
    551  1.29  christos 					cutlotime = lo;
    552  1.29  christos 				}
    553  1.29  christos 				if (hi < cuthitime) {
    554  1.54  christos 					if (hi < absolute_min_time + 1)
    555  1.54  christos 					  hi = absolute_min_time + 1;
    556  1.29  christos 					cuthitime = hi;
    557  1.29  christos 				}
    558  1.29  christos 			} else {
    559  1.29  christos 				(void) fprintf(stderr,
    560  1.29  christos 					_("%s: wild -t argument %s\n"),
    561  1.29  christos 					progname, cuttimes);
    562  1.36  christos 				return EXIT_FAILURE;
    563  1.29  christos 			}
    564  1.29  christos 		}
    565   1.1       jtc 	}
    566  1.36  christos 	gmtzinit();
    567  1.54  christos 	if (iflag | vflag | Vflag)
    568  1.54  christos 	  now = 0;
    569  1.54  christos 	else {
    570  1.44  christos 	  now = time(NULL);
    571  1.54  christos 	  now |= !now;
    572  1.54  christos 	}
    573  1.36  christos 	for (i = optind; i < argc; i++) {
    574  1.65  christos 	  size_t arglen = strlen(argv[i]);
    575  1.65  christos 	  if (arglenmax < arglen)
    576  1.65  christos 	    arglenmax = arglen;
    577  1.36  christos 	}
    578  1.65  christos 	if (!HAVE_SETENV && INDEX_MAX <= arglenmax)
    579  1.65  christos 	  size_overflow();
    580  1.65  christos 	longest = min(arglenmax, INT_MAX - 2);
    581   1.1       jtc 
    582   1.1       jtc 	for (i = optind; i < argc; ++i) {
    583  1.65  christos 		/* Treat "-" as standard input on platforms with /dev/stdin.
    584  1.65  christos 		   It's not worth the bother of supporting "-" on other
    585  1.65  christos 		   platforms, as that would need temp files.  */
    586  1.65  christos 		timezone_t tz = tzalloc(strcmp(argv[i], "-") == 0
    587  1.65  christos 					? "/dev/stdin" : argv[i]);
    588  1.36  christos 		char const *ab;
    589  1.44  christos 		time_t t;
    590  1.44  christos 		struct tm tm, newtm;
    591  1.44  christos 		bool tm_ok;
    592  1.44  christos 
    593  1.36  christos 		if (!tz) {
    594  1.62  christos 			err(EXIT_FAILURE, "%s", argv[i]);
    595  1.36  christos 		}
    596  1.54  christos 		if (now) {
    597  1.36  christos 			show(tz, argv[i], now, false);
    598  1.36  christos 			tzfree(tz);
    599   1.1       jtc 			continue;
    600   1.3       jtc 		}
    601  1.36  christos 		warned = false;
    602  1.17   mlelstv 		t = absolute_min_time;
    603  1.44  christos 		if (! (iflag | Vflag)) {
    604  1.36  christos 			show(tz, argv[i], t, true);
    605  1.54  christos 			if (my_localtime_rz(tz, &t, &tm) == NULL
    606  1.54  christos 			    && t < cutlotime) {
    607  1.54  christos 				time_t newt = cutlotime;
    608  1.54  christos 				if (my_localtime_rz(tz, &newt, &newtm) != NULL)
    609  1.54  christos 				  showextrema(tz, argv[i], t, NULL, newt);
    610  1.54  christos 			}
    611  1.29  christos 		}
    612  1.54  christos 		if (t + 1 < cutlotime)
    613  1.54  christos 		  t = cutlotime - 1;
    614  1.44  christos 		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
    615  1.44  christos 		if (tm_ok) {
    616  1.36  christos 			ab = saveabbr(&abbrev, &abbrevsize, &tm);
    617  1.44  christos 			if (iflag) {
    618  1.44  christos 				showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
    619  1.44  christos 				showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
    620  1.44  christos 			}
    621  1.44  christos 		} else
    622  1.36  christos 			ab = NULL;
    623  1.54  christos 		while (t < cuthitime - 1) {
    624  1.44  christos 			time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
    625  1.54  christos 			    && t + SECSPERDAY / 2 < cuthitime - 1)
    626  1.54  christos 			    ? t + SECSPERDAY / 2
    627  1.54  christos 			    : cuthitime - 1);
    628  1.44  christos 			struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
    629  1.44  christos 			bool newtm_ok = newtmp != NULL;
    630  1.48  christos 			if (tm_ok != newtm_ok
    631  1.54  christos 			    || (ab && (delta(&newtm, &tm) != newt - t
    632  1.54  christos 				       || newtm.tm_isdst != tm.tm_isdst
    633  1.54  christos 				       || strcmp(abbr(&newtm), ab) != 0))) {
    634  1.59  christos 				newt = hunt(tz, t, newt, false);
    635  1.36  christos 				newtmp = localtime_rz(tz, &newt, &newtm);
    636  1.44  christos 				newtm_ok = newtmp != NULL;
    637  1.44  christos 				if (iflag)
    638  1.44  christos 					showtrans("%Y-%m-%d\t%L\t%Q",
    639  1.44  christos 					    newtmp, newt, newtm_ok ?
    640  1.44  christos 					    abbr(&newtm) : NULL, argv[i]);
    641  1.44  christos 				else {
    642  1.44  christos 					show(tz, argv[i], newt - 1, true);
    643  1.44  christos 					show(tz, argv[i], newt, true);
    644  1.44  christos 				}
    645   1.1       jtc 			}
    646   1.1       jtc 			t = newt;
    647  1.44  christos 			tm_ok = newtm_ok;
    648  1.44  christos 			if (newtm_ok) {
    649  1.44  christos 				ab = saveabbr(&abbrev, &abbrevsize, &newtm);
    650  1.44  christos 				tm = newtm;
    651  1.44  christos 			}
    652   1.1       jtc 		}
    653  1.44  christos 		if (! (iflag | Vflag)) {
    654  1.54  christos 			time_t newt = absolute_max_time;
    655  1.54  christos 			t = cuthitime;
    656  1.54  christos 			if (t < newt) {
    657  1.54  christos 			  struct tm *tmp = my_localtime_rz(tz, &t, &tm);
    658  1.54  christos 			  if (tmp != NULL
    659  1.54  christos 			      && my_localtime_rz(tz, &newt, &newtm) == NULL)
    660  1.54  christos 			    showextrema(tz, argv[i], t, tmp, newt);
    661  1.54  christos 			}
    662  1.54  christos 			show(tz, argv[i], absolute_max_time, true);
    663  1.29  christos 		}
    664  1.36  christos 		tzfree(tz);
    665   1.1       jtc 	}
    666  1.36  christos 	close_file(stdout);
    667  1.36  christos 	if (errout && (ferror(stderr) || fclose(stderr) != 0))
    668  1.36  christos 		return EXIT_FAILURE;
    669  1.36  christos 	return EXIT_SUCCESS;
    670  1.17   mlelstv }
    671   1.1       jtc 
    672   1.1       jtc static time_t
    673  1.42  christos yeartot(intmax_t y)
    674  1.17   mlelstv {
    675  1.31  christos 	intmax_t	myy, seconds, years;
    676  1.29  christos 	time_t		t;
    677  1.17   mlelstv 
    678  1.17   mlelstv 	myy = EPOCH_YEAR;
    679  1.17   mlelstv 	t = 0;
    680  1.31  christos 	while (myy < y) {
    681  1.31  christos 		if (SECSPER400YEARS_FITS && 400 <= y - myy) {
    682  1.31  christos 			intmax_t diff400 = (y - myy) / 400;
    683  1.31  christos 			if (INTMAX_MAX / SECSPER400YEARS < diff400)
    684  1.31  christos 				return absolute_max_time;
    685  1.31  christos 			seconds = diff400 * SECSPER400YEARS;
    686  1.31  christos 			years = diff400 * 400;
    687  1.65  christos 		} else {
    688  1.17   mlelstv 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
    689  1.31  christos 			years = 1;
    690  1.31  christos 		}
    691  1.31  christos 		myy += years;
    692  1.31  christos 		if (t > absolute_max_time - seconds)
    693  1.31  christos 			return absolute_max_time;
    694  1.31  christos 		t += seconds;
    695  1.31  christos 	}
    696  1.31  christos 	while (y < myy) {
    697  1.31  christos 		if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
    698  1.31  christos 			intmax_t diff400 = (myy - y) / 400;
    699  1.31  christos 			if (INTMAX_MAX / SECSPER400YEARS < diff400)
    700  1.31  christos 				return absolute_min_time;
    701  1.31  christos 			seconds = diff400 * SECSPER400YEARS;
    702  1.31  christos 			years = diff400 * 400;
    703  1.17   mlelstv 		} else {
    704  1.31  christos 			seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
    705  1.31  christos 			years = 1;
    706  1.17   mlelstv 		}
    707  1.31  christos 		myy -= years;
    708  1.31  christos 		if (t < absolute_min_time + seconds)
    709  1.31  christos 			return absolute_min_time;
    710  1.31  christos 		t -= seconds;
    711  1.17   mlelstv 	}
    712  1.17   mlelstv 	return t;
    713  1.17   mlelstv }
    714  1.17   mlelstv 
    715  1.59  christos /* Search for a discontinuity in timezone TZ, in the
    716  1.54  christos    timestamps ranging from LOT through HIT.  LOT and HIT disagree
    717  1.54  christos    about some aspect of timezone.  If ONLY_OK, search only for
    718  1.54  christos    definedness changes, i.e., localtime succeeds on one side of the
    719  1.54  christos    transition but fails on the other side.  Return the timestamp just
    720  1.54  christos    before the transition from LOT's settings.  */
    721  1.54  christos 
    722  1.17   mlelstv static time_t
    723  1.59  christos hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
    724  1.17   mlelstv {
    725  1.36  christos 	static char *		loab;
    726  1.59  christos 	static ptrdiff_t	loabsize;
    727  1.17   mlelstv 	struct tm		lotm;
    728  1.17   mlelstv 	struct tm		tm;
    729  1.54  christos 
    730  1.54  christos 	/* Convert LOT into a broken-down time here, even though our
    731  1.54  christos 	   caller already did that.  On platforms without TM_ZONE,
    732  1.54  christos 	   tzname may have been altered since our caller broke down
    733  1.54  christos 	   LOT, and tzname needs to be changed back.  */
    734  1.44  christos 	bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
    735  1.44  christos 	bool tm_ok;
    736  1.54  christos 	char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
    737  1.17   mlelstv 
    738  1.17   mlelstv 	for ( ; ; ) {
    739  1.54  christos 		/* T = average of LOT and HIT, rounding down.
    740  1.62  christos 		   Avoid overflow.  */
    741  1.62  christos 		int rem_sum = lot % 2 + hit % 2;
    742  1.62  christos 		time_t t = (rem_sum == 2) - (rem_sum < 0) + lot / 2 + hit / 2;
    743  1.54  christos 		if (t == lot)
    744  1.17   mlelstv 			break;
    745  1.44  christos 		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
    746  1.54  christos 		if (lotm_ok == tm_ok
    747  1.54  christos 		    && (only_ok
    748  1.54  christos 			|| (ab && tm.tm_isdst == lotm.tm_isdst
    749  1.54  christos 			    && delta(&tm, &lotm) == t - lot
    750  1.54  christos 			    && strcmp(abbr(&tm), ab) == 0))) {
    751  1.44  christos 		  lot = t;
    752  1.44  christos 		  if (tm_ok)
    753  1.44  christos 		    lotm = tm;
    754   1.1       jtc 		} else	hit = t;
    755   1.1       jtc 	}
    756   1.1       jtc 	return hit;
    757   1.1       jtc }
    758   1.1       jtc 
    759   1.1       jtc /*
    760  1.47  christos ** Thanks to Paul Eggert for logic used in delta_nonneg.
    761   1.1       jtc */
    762   1.1       jtc 
    763  1.29  christos static intmax_t
    764  1.47  christos delta_nonneg(struct tm *newp, struct tm *oldp)
    765   1.1       jtc {
    766  1.54  christos 	intmax_t oldy = oldp->tm_year;
    767  1.54  christos 	int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
    768  1.54  christos 	intmax_t sec = SECSPERREPEAT, result = cycles * sec;
    769  1.54  christos 	int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
    770  1.54  christos 	for ( ; tmy < newp->tm_year; ++tmy)
    771  1.17   mlelstv 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
    772   1.1       jtc 	result += newp->tm_yday - oldp->tm_yday;
    773   1.1       jtc 	result *= HOURSPERDAY;
    774   1.1       jtc 	result += newp->tm_hour - oldp->tm_hour;
    775   1.1       jtc 	result *= MINSPERHOUR;
    776   1.1       jtc 	result += newp->tm_min - oldp->tm_min;
    777   1.1       jtc 	result *= SECSPERMIN;
    778   1.1       jtc 	result += newp->tm_sec - oldp->tm_sec;
    779   1.1       jtc 	return result;
    780   1.1       jtc }
    781   1.1       jtc 
    782  1.47  christos static intmax_t
    783  1.47  christos delta(struct tm *newp, struct tm *oldp)
    784  1.47  christos {
    785  1.47  christos   return (newp->tm_year < oldp->tm_year
    786  1.47  christos 	  ? -delta_nonneg(oldp, newp)
    787  1.47  christos 	  : delta_nonneg(newp, oldp));
    788  1.47  christos }
    789  1.47  christos 
    790  1.36  christos #ifndef TM_GMTOFF
    791  1.36  christos /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
    792  1.39  christos    Assume A and B differ by at most one year.  */
    793  1.36  christos static int
    794  1.36  christos adjusted_yday(struct tm const *a, struct tm const *b)
    795  1.36  christos {
    796  1.36  christos 	int yday = a->tm_yday;
    797  1.36  christos 	if (b->tm_year < a->tm_year)
    798  1.36  christos 		yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
    799  1.36  christos 	return yday;
    800  1.36  christos }
    801  1.36  christos #endif
    802  1.36  christos 
    803  1.48  christos /* If A is the broken-down local time and B the broken-down UT for
    804  1.48  christos    the same instant, return A's UT offset in seconds, where positive
    805  1.44  christos    offsets are east of Greenwich.  On failure, return LONG_MIN.
    806  1.44  christos 
    807  1.46  christos    If T is nonnull, *T is the timestamp that corresponds to A; call
    808  1.44  christos    my_gmtime_r and use its result instead of B.  Otherwise, B is the
    809  1.44  christos    possibly nonnull result of an earlier call to my_gmtime_r.  */
    810  1.36  christos static long
    811  1.59  christos gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
    812  1.59  christos        ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
    813  1.36  christos {
    814  1.36  christos #ifdef TM_GMTOFF
    815  1.36  christos 	return a->TM_GMTOFF;
    816  1.36  christos #else
    817  1.44  christos 	struct tm tm;
    818  1.44  christos 	if (t)
    819  1.44  christos 		b = my_gmtime_r(t, &tm);
    820  1.36  christos 	if (! b)
    821  1.36  christos 		return LONG_MIN;
    822  1.36  christos 	else {
    823  1.36  christos 		int ayday = adjusted_yday(a, b);
    824  1.36  christos 		int byday = adjusted_yday(b, a);
    825  1.36  christos 		int days = ayday - byday;
    826  1.36  christos 		long hours = a->tm_hour - b->tm_hour + 24 * days;
    827  1.36  christos 		long minutes = a->tm_min - b->tm_min + 60 * hours;
    828  1.36  christos 		long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
    829  1.36  christos 		return seconds;
    830  1.36  christos 	}
    831  1.36  christos #endif
    832  1.36  christos }
    833  1.36  christos 
    834   1.1       jtc static void
    835  1.36  christos show(timezone_t tz, char *zone, time_t t, bool v)
    836   1.1       jtc {
    837  1.26  christos 	struct tm *	tmp;
    838  1.36  christos 	struct tm *	gmtmp;
    839  1.36  christos 	struct tm tm, gmtm;
    840   1.1       jtc 
    841   1.5       jtc 	(void) printf("%-*s  ", (int) longest, zone);
    842   1.1       jtc 	if (v) {
    843  1.36  christos 		gmtmp = my_gmtime_r(&t, &gmtm);
    844  1.36  christos 		if (gmtmp == NULL) {
    845  1.36  christos 			printf(tformat(), t);
    846  1.56  christos 			printf(_(" (gmtime failed)"));
    847  1.17   mlelstv 		} else {
    848  1.36  christos 			dumptime(gmtmp);
    849  1.31  christos 			(void) printf(" UT");
    850  1.17   mlelstv 		}
    851  1.17   mlelstv 		(void) printf(" = ");
    852  1.17   mlelstv 	}
    853  1.36  christos 	tmp = my_localtime_rz(tz, &t, &tm);
    854  1.56  christos 	if (tmp == NULL) {
    855  1.56  christos 		printf(tformat(), t);
    856  1.56  christos 		printf(_(" (localtime failed)"));
    857  1.56  christos 	} else {
    858  1.56  christos 		dumptime(tmp);
    859  1.17   mlelstv 		if (*abbr(tmp) != '\0')
    860  1.17   mlelstv 			(void) printf(" %s", abbr(tmp));
    861  1.17   mlelstv 		if (v) {
    862  1.44  christos 			long off = gmtoff(tmp, NULL,  gmtmp);
    863  1.17   mlelstv 			(void) printf(" isdst=%d", tmp->tm_isdst);
    864  1.36  christos 			if (off != LONG_MIN)
    865  1.36  christos 				(void) printf(" gmtoff=%ld", off);
    866  1.17   mlelstv 		}
    867   1.1       jtc 	}
    868   1.1       jtc 	(void) printf("\n");
    869  1.17   mlelstv 	if (tmp != NULL && *abbr(tmp) != '\0')
    870  1.17   mlelstv 		abbrok(abbr(tmp), zone);
    871   1.1       jtc }
    872   1.1       jtc 
    873  1.54  christos /* Show timestamps just before and just after a transition between
    874  1.54  christos    defined and undefined (or vice versa) in either localtime or
    875  1.54  christos    gmtime.  These transitions are for timezone TZ with name ZONE, in
    876  1.54  christos    the range from LO (with broken-down time LOTMP if that is nonnull)
    877  1.54  christos    through HI.  LO and HI disagree on definedness.  */
    878  1.54  christos 
    879  1.54  christos static void
    880  1.54  christos showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
    881  1.54  christos {
    882  1.54  christos   struct tm localtm[2], gmtm[2];
    883  1.59  christos   time_t t, boundary = hunt(tz, lo, hi, true);
    884  1.54  christos   bool old = false;
    885  1.54  christos   hi = (SECSPERDAY < hi - boundary
    886  1.54  christos 	? boundary + SECSPERDAY
    887  1.54  christos 	: hi + (hi < TIME_T_MAX));
    888  1.54  christos   if (SECSPERDAY < boundary - lo) {
    889  1.54  christos     lo = boundary - SECSPERDAY;
    890  1.54  christos     lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
    891  1.54  christos   }
    892  1.54  christos   if (lotmp)
    893  1.54  christos     localtm[old] = *lotmp;
    894  1.54  christos   else
    895  1.54  christos     localtm[old].tm_sec = -1;
    896  1.54  christos   if (! my_gmtime_r(&lo, &gmtm[old]))
    897  1.54  christos     gmtm[old].tm_sec = -1;
    898  1.54  christos 
    899  1.54  christos   /* Search sequentially for definedness transitions.  Although this
    900  1.54  christos      could be sped up by refining 'hunt' to search for either
    901  1.54  christos      localtime or gmtime definedness transitions, it hardly seems
    902  1.54  christos      worth the trouble.  */
    903  1.54  christos   for (t = lo + 1; t < hi; t++) {
    904  1.54  christos     bool new = !old;
    905  1.54  christos     if (! my_localtime_rz(tz, &t, &localtm[new]))
    906  1.54  christos       localtm[new].tm_sec = -1;
    907  1.54  christos     if (! my_gmtime_r(&t, &gmtm[new]))
    908  1.54  christos       gmtm[new].tm_sec = -1;
    909  1.54  christos     if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
    910  1.54  christos 	| ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
    911  1.54  christos       show(tz, zone, t - 1, true);
    912  1.54  christos       show(tz, zone, t, true);
    913  1.54  christos     }
    914  1.54  christos     old = new;
    915  1.54  christos   }
    916  1.54  christos }
    917  1.54  christos 
    918  1.65  christos /* On pre-C99 platforms, a snprintf substitute good enough for us.  */
    919  1.65  christos #if !HAVE_SNPRINTF
    920  1.47  christos # include <stdarg.h>
    921  1.65  christos ATTRIBUTE_FORMAT((printf, 3, 4)) static int
    922  1.48  christos my_snprintf(char *s, size_t size, char const *format, ...)
    923  1.47  christos {
    924  1.47  christos   int n;
    925  1.47  christos   va_list args;
    926  1.47  christos   char const *arg;
    927  1.47  christos   size_t arglen, slen;
    928  1.47  christos   char buf[1024];
    929  1.47  christos   va_start(args, format);
    930  1.47  christos   if (strcmp(format, "%s") == 0) {
    931  1.47  christos     arg = va_arg(args, char const *);
    932  1.47  christos     arglen = strlen(arg);
    933  1.47  christos   } else {
    934  1.47  christos     n = vsprintf(buf, format, args);
    935  1.51  christos     if (n < 0) {
    936  1.51  christos       va_end(args);
    937  1.47  christos       return n;
    938  1.51  christos     }
    939  1.47  christos     arg = buf;
    940  1.47  christos     arglen = n;
    941  1.47  christos   }
    942  1.47  christos   slen = arglen < size ? arglen : size - 1;
    943  1.47  christos   memcpy(s, arg, slen);
    944  1.47  christos   s[slen] = '\0';
    945  1.47  christos   n = arglen <= INT_MAX ? arglen : -1;
    946  1.47  christos   va_end(args);
    947  1.47  christos   return n;
    948  1.47  christos }
    949  1.65  christos # define snprintf my_snprintf
    950  1.47  christos #endif
    951  1.47  christos 
    952  1.44  christos /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
    953  1.44  christos    Use ISO 8601 format +HH:MM:SS.  Omit :SS if SS is zero, and omit
    954  1.44  christos    :MM too if MM is also zero.
    955  1.44  christos 
    956  1.44  christos    Return the length of the resulting string.  If the string does not
    957  1.44  christos    fit, return the length that the string would have been if it had
    958  1.44  christos    fit; do not overrun the output buffer.  */
    959  1.44  christos static int
    960  1.59  christos format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
    961  1.44  christos {
    962  1.44  christos   int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
    963  1.44  christos   return (ss
    964  1.65  christos 	  ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
    965  1.44  christos 	  : mm
    966  1.65  christos 	  ? snprintf(buf, size, "%02d:%02d", hh, mm)
    967  1.65  christos 	  : snprintf(buf, size, "%02d", hh));
    968  1.44  christos }
    969  1.44  christos 
    970  1.48  christos /* Store into BUF, of size SIZE, a formatted UT offset for the
    971  1.44  christos    localtime *TM corresponding to time T.  Use ISO 8601 format
    972  1.46  christos    +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
    973  1.48  christos    format -00 for unknown UT offsets.  If the hour needs more than
    974  1.46  christos    two digits to represent, extend the length of HH as needed.
    975  1.46  christos    Otherwise, omit SS if SS is zero, and omit MM too if MM is also
    976  1.46  christos    zero.
    977  1.44  christos 
    978  1.44  christos    Return the length of the resulting string, or -1 if the result is
    979  1.44  christos    not representable as a string.  If the string does not fit, return
    980  1.44  christos    the length that the string would have been if it had fit; do not
    981  1.44  christos    overrun the output buffer.  */
    982  1.44  christos static int
    983  1.59  christos format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
    984  1.44  christos {
    985  1.44  christos   long off = gmtoff(tm, &t, NULL);
    986  1.44  christos   char sign = ((off < 0
    987  1.44  christos 		|| (off == 0
    988  1.44  christos 		    && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
    989  1.44  christos 	       ? '-' : '+');
    990  1.44  christos   long hh;
    991  1.44  christos   int mm, ss;
    992  1.44  christos   if (off < 0)
    993  1.44  christos     {
    994  1.44  christos       if (off == LONG_MIN)
    995  1.44  christos 	return -1;
    996  1.44  christos       off = -off;
    997  1.44  christos     }
    998  1.44  christos   ss = off % 60;
    999  1.44  christos   mm = off / 60 % 60;
   1000  1.44  christos   hh = off / 60 / 60;
   1001  1.46  christos   return (ss || 100 <= hh
   1002  1.65  christos 	  ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
   1003  1.44  christos 	  : mm
   1004  1.65  christos 	  ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
   1005  1.65  christos 	  : snprintf(buf, size, "%c%02ld", sign, hh));
   1006  1.44  christos }
   1007  1.44  christos 
   1008  1.44  christos /* Store into BUF (of size SIZE) a quoted string representation of P.
   1009  1.44  christos    If the representation's length is less than SIZE, return the
   1010  1.44  christos    length; the representation is not null terminated.  Otherwise
   1011  1.44  christos    return SIZE, to indicate that BUF is too small.  */
   1012  1.59  christos static ptrdiff_t
   1013  1.59  christos format_quoted_string(char *buf, ptrdiff_t size, char const *p)
   1014  1.44  christos {
   1015  1.44  christos   char *b = buf;
   1016  1.59  christos   ptrdiff_t s = size;
   1017  1.44  christos   if (!s)
   1018  1.44  christos     return size;
   1019  1.44  christos   *b++ = '"', s--;
   1020  1.44  christos   for (;;) {
   1021  1.44  christos     char c = *p++;
   1022  1.44  christos     if (s <= 1)
   1023  1.44  christos       return size;
   1024  1.44  christos     switch (c) {
   1025  1.44  christos     default: *b++ = c, s--; continue;
   1026  1.44  christos     case '\0': *b++ = '"', s--; return size - s;
   1027  1.44  christos     case '"': case '\\': break;
   1028  1.44  christos     case ' ': c = 's'; break;
   1029  1.44  christos     case '\f': c = 'f'; break;
   1030  1.44  christos     case '\n': c = 'n'; break;
   1031  1.44  christos     case '\r': c = 'r'; break;
   1032  1.44  christos     case '\t': c = 't'; break;
   1033  1.44  christos     case '\v': c = 'v'; break;
   1034  1.44  christos     }
   1035  1.44  christos     *b++ = '\\', *b++ = c, s -= 2;
   1036  1.44  christos   }
   1037  1.44  christos }
   1038  1.44  christos 
   1039  1.46  christos /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
   1040  1.44  christos    TM is the broken-down time, T the seconds count, AB the time zone
   1041  1.44  christos    abbreviation, and ZONE_NAME the zone name.  Return true if
   1042  1.44  christos    successful, false if the output would require more than SIZE bytes.
   1043  1.44  christos    TIME_FMT uses the same format that strftime uses, with these
   1044  1.44  christos    additions:
   1045  1.44  christos 
   1046  1.44  christos    %f zone name
   1047  1.44  christos    %L local time as per format_local_time
   1048  1.48  christos    %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
   1049  1.44  christos       and D is the isdst flag; except omit D if it is zero, omit %Z if
   1050  1.44  christos       it equals U, quote and escape %Z if it contains nonalphabetics,
   1051  1.44  christos       and omit any trailing tabs.  */
   1052  1.44  christos 
   1053  1.44  christos static bool
   1054  1.59  christos istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
   1055  1.44  christos 	  struct tm const *tm, time_t t, char const *ab, char const *zone_name)
   1056  1.44  christos {
   1057  1.44  christos   char *b = buf;
   1058  1.59  christos   ptrdiff_t s = size;
   1059  1.44  christos   char const *f = time_fmt, *p;
   1060  1.44  christos 
   1061  1.44  christos   for (p = f; ; p++)
   1062  1.44  christos     if (*p == '%' && p[1] == '%')
   1063  1.44  christos       p++;
   1064  1.44  christos     else if (!*p
   1065  1.44  christos 	     || (*p == '%'
   1066  1.44  christos 		 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
   1067  1.59  christos       ptrdiff_t formatted_len;
   1068  1.59  christos       ptrdiff_t f_prefix_len = p - f;
   1069  1.59  christos       ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
   1070  1.44  christos       char fbuf[100];
   1071  1.59  christos       bool oversized = (ptrdiff_t)sizeof fbuf <= f_prefix_copy_size;
   1072  1.44  christos       char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
   1073  1.44  christos       memcpy(f_prefix_copy, f, f_prefix_len);
   1074  1.44  christos       strcpy(f_prefix_copy + f_prefix_len, "X");
   1075  1.44  christos       formatted_len = strftime(b, s, f_prefix_copy, tm);
   1076  1.44  christos       if (oversized)
   1077  1.44  christos 	free(f_prefix_copy);
   1078  1.44  christos       if (formatted_len == 0)
   1079  1.44  christos 	return false;
   1080  1.44  christos       formatted_len--;
   1081  1.44  christos       b += formatted_len, s -= formatted_len;
   1082  1.44  christos       if (!*p++)
   1083  1.44  christos 	break;
   1084  1.44  christos       switch (*p) {
   1085  1.44  christos       case 'f':
   1086  1.44  christos 	formatted_len = format_quoted_string(b, s, zone_name);
   1087  1.44  christos 	break;
   1088  1.44  christos       case 'L':
   1089  1.44  christos 	formatted_len = format_local_time(b, s, tm);
   1090  1.44  christos 	break;
   1091  1.44  christos       case 'Q':
   1092  1.44  christos 	{
   1093  1.44  christos 	  bool show_abbr;
   1094  1.44  christos 	  int offlen = format_utc_offset(b, s, tm, t);
   1095  1.59  christos 	  if (! (0 <= offlen && offlen < s))
   1096  1.44  christos 	    return false;
   1097  1.44  christos 	  show_abbr = strcmp(b, ab) != 0;
   1098  1.44  christos 	  b += offlen, s -= offlen;
   1099  1.44  christos 	  if (show_abbr) {
   1100  1.44  christos 	    char const *abp;
   1101  1.59  christos 	    ptrdiff_t len;
   1102  1.44  christos 	    if (s <= 1)
   1103  1.44  christos 	      return false;
   1104  1.44  christos 	    *b++ = '\t', s--;
   1105  1.44  christos 	    for (abp = ab; is_alpha(*abp); abp++)
   1106  1.44  christos 	      continue;
   1107  1.44  christos 	    len = (!*abp && *ab
   1108  1.65  christos 		   ? snprintf(b, s, "%s", ab)
   1109  1.44  christos 		   : format_quoted_string(b, s, ab));
   1110  1.44  christos 	    if (s <= len)
   1111  1.44  christos 	      return false;
   1112  1.44  christos 	    b += len, s -= len;
   1113  1.44  christos 	  }
   1114  1.48  christos 	  formatted_len
   1115  1.48  christos 	    = (tm->tm_isdst
   1116  1.65  christos 	       ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
   1117  1.48  christos 	       : 0);
   1118  1.44  christos 	}
   1119  1.44  christos 	break;
   1120  1.44  christos       }
   1121  1.46  christos       if (s <= formatted_len)
   1122  1.44  christos 	return false;
   1123  1.44  christos       b += formatted_len, s -= formatted_len;
   1124  1.44  christos       f = p + 1;
   1125  1.44  christos     }
   1126  1.44  christos   *b = '\0';
   1127  1.44  christos   return true;
   1128  1.44  christos }
   1129  1.44  christos 
   1130  1.44  christos /* Show a time transition.  */
   1131  1.44  christos static void
   1132  1.44  christos showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
   1133  1.44  christos 	  char const *zone_name)
   1134  1.44  christos {
   1135  1.44  christos   if (!tm) {
   1136  1.44  christos     printf(tformat(), t);
   1137  1.44  christos     putchar('\n');
   1138  1.44  christos   } else {
   1139  1.44  christos     char stackbuf[1000];
   1140  1.59  christos     ptrdiff_t size = sizeof stackbuf;
   1141  1.44  christos     char *buf = stackbuf;
   1142  1.44  christos     char *bufalloc = NULL;
   1143  1.44  christos     while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
   1144  1.44  christos       size = sumsize(size, size);
   1145  1.44  christos       free(bufalloc);
   1146  1.44  christos       buf = bufalloc = xmalloc(size);
   1147  1.44  christos     }
   1148  1.44  christos     puts(buf);
   1149  1.44  christos     free(bufalloc);
   1150  1.44  christos   }
   1151  1.44  christos }
   1152  1.44  christos 
   1153   1.9   mycroft static const char *
   1154  1.36  christos abbr(struct tm const *tmp)
   1155   1.1       jtc {
   1156  1.36  christos #ifdef TM_ZONE
   1157  1.36  christos 	return tmp->TM_ZONE;
   1158  1.36  christos #else
   1159  1.47  christos # if HAVE_TZNAME
   1160  1.47  christos 	if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
   1161  1.47  christos 	  return tzname[0 < tmp->tm_isdst];
   1162  1.47  christos # endif
   1163  1.47  christos 	return "";
   1164  1.36  christos #endif
   1165   1.1       jtc }
   1166  1.17   mlelstv 
   1167  1.17   mlelstv /*
   1168  1.17   mlelstv ** The code below can fail on certain theoretical systems;
   1169  1.56  christos ** it works on all known real-world systems as of 2022-01-25.
   1170  1.17   mlelstv */
   1171  1.17   mlelstv 
   1172  1.17   mlelstv static const char *
   1173  1.17   mlelstv tformat(void)
   1174  1.17   mlelstv {
   1175  1.62  christos #if HAVE__GENERIC
   1176  1.56  christos 	/* C11-style _Generic is more likely to return the correct
   1177  1.56  christos 	   format when distinct types have the same size.  */
   1178  1.56  christos 	char const *fmt =
   1179  1.56  christos 	  _Generic(+ (time_t) 0,
   1180  1.56  christos 		   int: "%d", long: "%ld", long long: "%lld",
   1181  1.56  christos 		   unsigned: "%u", unsigned long: "%lu",
   1182  1.56  christos 		   unsigned long long: "%llu",
   1183  1.56  christos 		   default: NULL);
   1184  1.56  christos 	if (fmt)
   1185  1.56  christos 	  return fmt;
   1186  1.56  christos 	fmt = _Generic((time_t) 0,
   1187  1.56  christos 		       intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX,
   1188  1.56  christos 		       default: NULL);
   1189  1.56  christos 	if (fmt)
   1190  1.56  christos 	  return fmt;
   1191  1.56  christos #endif
   1192  1.17   mlelstv 	if (0 > (time_t) -1) {		/* signed */
   1193  1.54  christos 		if (sizeof(time_t) == sizeof(intmax_t))
   1194  1.29  christos 			return "%"PRIdMAX;
   1195  1.54  christos 		if (sizeof(time_t) > sizeof(long))
   1196  1.17   mlelstv 			return "%lld";
   1197  1.54  christos 		if (sizeof(time_t) > sizeof(int))
   1198  1.17   mlelstv 			return "%ld";
   1199  1.17   mlelstv 		return "%d";
   1200  1.17   mlelstv 	}
   1201  1.29  christos #ifdef PRIuMAX
   1202  1.54  christos 	if (sizeof(time_t) == sizeof(uintmax_t))
   1203  1.29  christos 		return "%"PRIuMAX;
   1204  1.29  christos #endif
   1205  1.54  christos 	if (sizeof(time_t) > sizeof(unsigned long))
   1206  1.17   mlelstv 		return "%llu";
   1207  1.54  christos 	if (sizeof(time_t) > sizeof(unsigned int))
   1208  1.17   mlelstv 		return "%lu";
   1209  1.17   mlelstv 	return "%u";
   1210  1.17   mlelstv }
   1211  1.17   mlelstv 
   1212  1.17   mlelstv static void
   1213  1.26  christos dumptime(const struct tm *timeptr)
   1214  1.17   mlelstv {
   1215  1.47  christos 	static const char	wday_name[][4] = {
   1216  1.17   mlelstv 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
   1217  1.17   mlelstv 	};
   1218  1.47  christos 	static const char	mon_name[][4] = {
   1219  1.17   mlelstv 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
   1220  1.17   mlelstv 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
   1221  1.17   mlelstv 	};
   1222  1.26  christos 	int		lead;
   1223  1.26  christos 	int		trail;
   1224  1.57  christos 	int DIVISOR = 10;
   1225  1.17   mlelstv 
   1226  1.17   mlelstv 	/*
   1227  1.36  christos 	** The packaged localtime_rz and gmtime_r never put out-of-range
   1228  1.17   mlelstv 	** values in tm_wday or tm_mon, but since this code might be compiled
   1229  1.17   mlelstv 	** with other (perhaps experimental) versions, paranoia is in order.
   1230  1.17   mlelstv 	*/
   1231  1.47  christos 	printf("%s %s%3d %.2d:%.2d:%.2d ",
   1232  1.54  christos 		((0 <= timeptr->tm_wday
   1233  1.55     ryoon 		  && timeptr->tm_wday < (int) (sizeof wday_name / sizeof wday_name[0]))
   1234  1.54  christos 		 ? wday_name[timeptr->tm_wday] : "???"),
   1235  1.54  christos 		((0 <= timeptr->tm_mon
   1236  1.55     ryoon 		  && timeptr->tm_mon < (int) (sizeof mon_name / sizeof mon_name[0]))
   1237  1.54  christos 		 ? mon_name[timeptr->tm_mon] : "???"),
   1238  1.17   mlelstv 		timeptr->tm_mday, timeptr->tm_hour,
   1239  1.17   mlelstv 		timeptr->tm_min, timeptr->tm_sec);
   1240  1.17   mlelstv 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
   1241  1.17   mlelstv 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
   1242  1.17   mlelstv 		trail / DIVISOR;
   1243  1.17   mlelstv 	trail %= DIVISOR;
   1244  1.17   mlelstv 	if (trail < 0 && lead > 0) {
   1245  1.17   mlelstv 		trail += DIVISOR;
   1246  1.17   mlelstv 		--lead;
   1247  1.17   mlelstv 	} else if (lead < 0 && trail > 0) {
   1248  1.17   mlelstv 		trail -= DIVISOR;
   1249  1.17   mlelstv 		++lead;
   1250  1.17   mlelstv 	}
   1251  1.17   mlelstv 	if (lead == 0)
   1252  1.36  christos 		printf("%d", trail);
   1253  1.36  christos 	else	printf("%d%d", lead, ((trail < 0) ? -trail : trail));
   1254  1.17   mlelstv }
   1255