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