S390: Fix building with --disable-mutli-arch [BZ #31196]
[platform/upstream/glibc.git] / timezone / zdump.c
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)
332           wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
333         else if (cp - abbrp < 3)
334           wp = _("has fewer than 3 characters");
335         else if (cp - abbrp > 6)
336           wp = _("has more than 6 characters");
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 }