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