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