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