"Fossies" - the Fresh Open Source Software Archive

Member "zdump.c" (12 Dec 2018, 28519 Bytes) of package /linux/misc/tzcode2018i.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "zdump.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2018g_vs_2018h.

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