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