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