"Fossies" - the Fresh Open Source Software Archive

Member "zdump.c" (21 Nov 2022, 33757 Bytes) of package /linux/misc/tzcode2022g.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. See also the latest Fossies "Diffs" side-by-side code changes report for "zdump.c": 2022f_vs_2022g.

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