xref: /netbsd-src/lib/libc/time/strftime.c (revision f21b7d7f2cbdd5c14b3882c4e8a3d43580d460a6)
1 /*	$NetBSD: strftime.c,v 1.37 2016/10/07 15:29:42 christos Exp $	*/
2 
3 /* Convert a broken-down time stamp to a string.  */
4 
5 /* Copyright 1989 The Regents of the University of California.
6    All rights reserved.
7 
8    Redistribution and use in source and binary forms, with or without
9    modification, are permitted provided that the following conditions
10    are met:
11    1. Redistributions of source code must retain the above copyright
12       notice, this list of conditions and the following disclaimer.
13    2. Redistributions in binary form must reproduce the above copyright
14       notice, this list of conditions and the following disclaimer in the
15       documentation and/or other materials provided with the distribution.
16    3. Neither the name of the University nor the names of its contributors
17       may be used to endorse or promote products derived from this software
18       without specific prior written permission.
19 
20    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
21    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23    ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30    SUCH DAMAGE.  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 #if 0
35 static char	elsieid[] = "@(#)strftime.c	7.64";
36 static char	elsieid[] = "@(#)strftime.c	8.3";
37 #else
38 __RCSID("$NetBSD: strftime.c,v 1.37 2016/10/07 15:29:42 christos Exp $");
39 #endif
40 #endif /* LIBC_SCCS and not lint */
41 
42 #include "namespace.h"
43 
44 #include <stddef.h>
45 #include <assert.h>
46 #include <locale.h>
47 #include "setlocale_local.h"
48 
49 /*
50 ** Based on the UCB version with the copyright notice appearing above.
51 **
52 ** This is ANSIish only when "multibyte character == plain character".
53 */
54 
55 #include "private.h"
56 
57 /*
58 ** We don't use these extensions in strftime operation even when
59 ** supported by the local tzcode configuration.  A strictly
60 ** conforming C application may leave them in undefined state.
61 */
62 
63 #ifdef _LIBC
64 #undef TM_ZONE
65 #undef TM_GMTOFF
66 #endif
67 
68 #include "tzfile.h"
69 #include "fcntl.h"
70 #include "locale.h"
71 
72 #ifdef __weak_alias
73 __weak_alias(strftime_l, _strftime_l)
74 __weak_alias(strftime_lz, _strftime_lz)
75 __weak_alias(strftime_z, _strftime_z)
76 #endif
77 
78 #include "sys/localedef.h"
79 #define _TIME_LOCALE(loc) \
80     ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME]))
81 #define c_fmt   d_t_fmt
82 
83 static char *	_add(const char *, char *, const char *);
84 static char *	_conv(int, const char *, char *, const char *);
85 static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
86 			const char *, int *, locale_t);
87 static char *	_yconv(int, int, bool, bool, char *, const char *);
88 
89 #if !HAVE_POSIX_DECLS
90 extern char *	tzname[];
91 #endif
92 
93 #ifndef YEAR_2000_NAME
94 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
95 #endif /* !defined YEAR_2000_NAME */
96 
97 #define IN_NONE	0
98 #define IN_SOME	1
99 #define IN_THIS	2
100 #define IN_ALL	3
101 
102 size_t
103 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
104     const char * __restrict format, const struct tm * __restrict t)
105 {
106 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
107 }
108 
109 #if HAVE_STRFTIME_L
110 size_t
111 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
112 	   locale_t locale)
113 {
114   /* Just call strftime, as only the C locale is supported.  */
115   return strftime(s, maxsize, format, t);
116 }
117 #endif
118 
119 size_t
120 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
121     const char *const format, const struct tm *const t, locale_t loc)
122 {
123 	char *	p;
124 	int	warn;
125 
126 	warn = IN_NONE;
127 	p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
128 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
129 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
130 		(void) fprintf(stderr, "\n");
131 		(void) fprintf(stderr, "strftime format \"%s\" ", format);
132 		(void) fprintf(stderr, "yields only two digits of years in ");
133 		if (warn == IN_SOME)
134 			(void) fprintf(stderr, "some locales");
135 		else if (warn == IN_THIS)
136 			(void) fprintf(stderr, "the current locale");
137 		else	(void) fprintf(stderr, "all locales");
138 		(void) fprintf(stderr, "\n");
139 	}
140 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
141 	if (p == s + maxsize)
142 		return 0;
143 	*p = '\0';
144 	return p - s;
145 }
146 
147 static char *
148 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
149      const char *ptlim, int *warnp, locale_t loc)
150 {
151 	for ( ; *format; ++format) {
152 		if (*format == '%') {
153 label:
154 			switch (*++format) {
155 			case '\0':
156 				--format;
157 				break;
158 			case 'A':
159 				pt = _add((t->tm_wday < 0 ||
160 					t->tm_wday >= DAYSPERWEEK) ?
161 					"?" : _TIME_LOCALE(loc)->day[t->tm_wday],
162 					pt, ptlim);
163 				continue;
164 			case 'a':
165 				pt = _add((t->tm_wday < 0 ||
166 					t->tm_wday >= DAYSPERWEEK) ?
167 					"?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
168 					pt, ptlim);
169 				continue;
170 			case 'B':
171 				pt = _add((t->tm_mon < 0 ||
172 					t->tm_mon >= MONSPERYEAR) ?
173 					"?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
174 					pt, ptlim);
175 				continue;
176 			case 'b':
177 			case 'h':
178 				pt = _add((t->tm_mon < 0 ||
179 					t->tm_mon >= MONSPERYEAR) ?
180 					"?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
181 					pt, ptlim);
182 				continue;
183 			case 'C':
184 				/*
185 				** %C used to do a...
186 				**	_fmt("%a %b %e %X %Y", t);
187 				** ...whereas now POSIX 1003.2 calls for
188 				** something completely different.
189 				** (ado, 1993-05-24)
190 				*/
191 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
192 					    true, false, pt, ptlim);
193 				continue;
194 			case 'c':
195 				{
196 				int warn2 = IN_SOME;
197 
198 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
199 				    ptlim, &warn2, loc);
200 				if (warn2 == IN_ALL)
201 					warn2 = IN_THIS;
202 				if (warn2 > *warnp)
203 					*warnp = warn2;
204 				}
205 				continue;
206 			case 'D':
207 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
208 				    loc);
209 				continue;
210 			case 'd':
211 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
212 				continue;
213 			case 'E':
214 			case 'O':
215 				/*
216 				** C99 locale modifiers.
217 				** The sequences
218 				**	%Ec %EC %Ex %EX %Ey %EY
219 				**	%Od %oe %OH %OI %Om %OM
220 				**	%OS %Ou %OU %OV %Ow %OW %Oy
221 				** are supposed to provide alternate
222 				** representations.
223 				*/
224 				goto label;
225 			case 'e':
226 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
227 				continue;
228 			case 'F':
229 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
230 				    loc);
231 				continue;
232 			case 'H':
233 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
234 				continue;
235 			case 'I':
236 				pt = _conv((t->tm_hour % 12) ?
237 					(t->tm_hour % 12) : 12,
238 					"%02d", pt, ptlim);
239 				continue;
240 			case 'j':
241 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
242 				continue;
243 			case 'k':
244 				/*
245 				** This used to be...
246 				**	_conv(t->tm_hour % 12 ?
247 				**		t->tm_hour % 12 : 12, 2, ' ');
248 				** ...and has been changed to the below to
249 				** match SunOS 4.1.1 and Arnold Robbins'
250 				** strftime version 3.0. That is, "%k" and
251 				** "%l" have been swapped.
252 				** (ado, 1993-05-24)
253 				*/
254 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
255 				continue;
256 #ifdef KITCHEN_SINK
257 			case 'K':
258 				/*
259 				** After all this time, still unclaimed!
260 				*/
261 				pt = _add("kitchen sink", pt, ptlim);
262 				continue;
263 #endif /* defined KITCHEN_SINK */
264 			case 'l':
265 				/*
266 				** This used to be...
267 				**	_conv(t->tm_hour, 2, ' ');
268 				** ...and has been changed to the below to
269 				** match SunOS 4.1.1 and Arnold Robbin's
270 				** strftime version 3.0. That is, "%k" and
271 				** "%l" have been swapped.
272 				** (ado, 1993-05-24)
273 				*/
274 				pt = _conv((t->tm_hour % 12) ?
275 					(t->tm_hour % 12) : 12,
276 					"%2d", pt, ptlim);
277 				continue;
278 			case 'M':
279 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
280 				continue;
281 			case 'm':
282 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
283 				continue;
284 			case 'n':
285 				pt = _add("\n", pt, ptlim);
286 				continue;
287 			case 'p':
288 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
289 					_TIME_LOCALE(loc)->am_pm[1] :
290 					_TIME_LOCALE(loc)->am_pm[0],
291 					pt, ptlim);
292 				continue;
293 			case 'R':
294 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
295 				    loc);
296 				continue;
297 			case 'r':
298 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
299 				    pt, ptlim, warnp, loc);
300 				continue;
301 			case 'S':
302 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
303 				continue;
304 			case 's':
305 				{
306 					struct tm	tm;
307 					char		buf[INT_STRLEN_MAXIMUM(
308 								time_t) + 1];
309 					time_t		mkt;
310 
311 					tm = *t;
312 					mkt = mktime(&tm);
313 					/* CONSTCOND */
314 					if (TYPE_SIGNED(time_t))
315 						(void)snprintf(buf, sizeof(buf),
316 						    "%jd", (intmax_t) mkt);
317 					else	(void)snprintf(buf, sizeof(buf),
318 						    "%ju", (uintmax_t) mkt);
319 					pt = _add(buf, pt, ptlim);
320 				}
321 				continue;
322 			case 'T':
323 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
324 				    loc);
325 				continue;
326 			case 't':
327 				pt = _add("\t", pt, ptlim);
328 				continue;
329 			case 'U':
330 				pt = _conv((t->tm_yday + DAYSPERWEEK -
331 					t->tm_wday) / DAYSPERWEEK,
332 					"%02d", pt, ptlim);
333 				continue;
334 			case 'u':
335 				/*
336 				** From Arnold Robbins' strftime version 3.0:
337 				** "ISO 8601: Weekday as a decimal number
338 				** [1 (Monday) - 7]"
339 				** (ado, 1993-05-24)
340 				*/
341 				pt = _conv((t->tm_wday == 0) ?
342 					DAYSPERWEEK : t->tm_wday,
343 					"%d", pt, ptlim);
344 				continue;
345 			case 'V':	/* ISO 8601 week number */
346 			case 'G':	/* ISO 8601 year (four digits) */
347 			case 'g':	/* ISO 8601 year (two digits) */
348 /*
349 ** From Arnold Robbins' strftime version 3.0: "the week number of the
350 ** year (the first Monday as the first day of week 1) as a decimal number
351 ** (01-53)."
352 ** (ado, 1993-05-24)
353 **
354 ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
355 ** "Week 01 of a year is per definition the first week which has the
356 ** Thursday in this year, which is equivalent to the week which contains
357 ** the fourth day of January. In other words, the first week of a new year
358 ** is the week which has the majority of its days in the new year. Week 01
359 ** might also contain days from the previous year and the week before week
360 ** 01 of a year is the last week (52 or 53) of the previous year even if
361 ** it contains days from the new year. A week starts with Monday (day 1)
362 ** and ends with Sunday (day 7). For example, the first week of the year
363 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
364 ** (ado, 1996-01-02)
365 */
366 				{
367 					int	year;
368 					int	base;
369 					int	yday;
370 					int	wday;
371 					int	w;
372 
373 					year = t->tm_year;
374 					base = TM_YEAR_BASE;
375 					yday = t->tm_yday;
376 					wday = t->tm_wday;
377 					for ( ; ; ) {
378 						int	len;
379 						int	bot;
380 						int	top;
381 
382 						len = isleap_sum(year, base) ?
383 							DAYSPERLYEAR :
384 							DAYSPERNYEAR;
385 						/*
386 						** What yday (-3 ... 3) does
387 						** the ISO year begin on?
388 						*/
389 						bot = ((yday + 11 - wday) %
390 							DAYSPERWEEK) - 3;
391 						/*
392 						** What yday does the NEXT
393 						** ISO year begin on?
394 						*/
395 						top = bot -
396 							(len % DAYSPERWEEK);
397 						if (top < -3)
398 							top += DAYSPERWEEK;
399 						top += len;
400 						if (yday >= top) {
401 							++base;
402 							w = 1;
403 							break;
404 						}
405 						if (yday >= bot) {
406 							w = 1 + ((yday - bot) /
407 								DAYSPERWEEK);
408 							break;
409 						}
410 						--base;
411 						yday += isleap_sum(year, base) ?
412 							DAYSPERLYEAR :
413 							DAYSPERNYEAR;
414 					}
415 #ifdef XPG4_1994_04_09
416 					if ((w == 52 &&
417 						t->tm_mon == TM_JANUARY) ||
418 						(w == 1 &&
419 						t->tm_mon == TM_DECEMBER))
420 							w = 53;
421 #endif /* defined XPG4_1994_04_09 */
422 					if (*format == 'V')
423 						pt = _conv(w, "%02d",
424 							pt, ptlim);
425 					else if (*format == 'g') {
426 						*warnp = IN_ALL;
427 						pt = _yconv(year, base,
428 							false, true,
429 							pt, ptlim);
430 					} else	pt = _yconv(year, base,
431 							true, true,
432 							pt, ptlim);
433 				}
434 				continue;
435 			case 'v':
436 				/*
437 				** From Arnold Robbins' strftime version 3.0:
438 				** "date as dd-bbb-YYYY"
439 				** (ado, 1993-05-24)
440 				*/
441 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
442 				    loc);
443 				continue;
444 			case 'W':
445 				pt = _conv((t->tm_yday + DAYSPERWEEK -
446 					(t->tm_wday ?
447 					(t->tm_wday - 1) :
448 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
449 					"%02d", pt, ptlim);
450 				continue;
451 			case 'w':
452 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
453 				continue;
454 			case 'X':
455 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
456 				    ptlim, warnp, loc);
457 				continue;
458 			case 'x':
459 				{
460 				int	warn2 = IN_SOME;
461 
462 				pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
463 				    ptlim, &warn2, loc);
464 				if (warn2 == IN_ALL)
465 					warn2 = IN_THIS;
466 				if (warn2 > *warnp)
467 					*warnp = warn2;
468 				}
469 				continue;
470 			case 'y':
471 				*warnp = IN_ALL;
472 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
473 					false, true,
474 					pt, ptlim);
475 				continue;
476 			case 'Y':
477 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
478 					true, true,
479 					pt, ptlim);
480 				continue;
481 			case 'Z':
482 #ifdef TM_ZONE
483 				pt = _add(t->TM_ZONE, pt, ptlim);
484 #endif /* defined TM_ZONE */
485 				if (t->tm_isdst >= 0)
486 					pt = _add((sp ?
487 					    tzgetname(sp, t->tm_isdst) :
488 					    tzname[t->tm_isdst != 0]),
489 					    pt, ptlim);
490 				/*
491 				** C99 says that %Z must be replaced by the
492 				** empty string if the time zone is not
493 				** determinable.
494 				*/
495 				continue;
496 			case 'z':
497 				{
498 				long		diff;
499 				char const *	sign;
500 
501 				if (t->tm_isdst < 0)
502 					continue;
503 #ifdef TM_GMTOFF
504 				diff = (int)t->TM_GMTOFF;
505 #else /* !defined TM_GMTOFF */
506 				/*
507 				** C99 says that the UT offset must
508 				** be computed by looking only at
509 				** tm_isdst. This requirement is
510 				** incorrect, since it means the code
511 				** must rely on magic (in this case
512 				** altzone and timezone), and the
513 				** magic might not have the correct
514 				** offset. Doing things correctly is
515 				** tricky and requires disobeying C99;
516 				** see GNU C strftime for details.
517 				** For now, punt and conform to the
518 				** standard, even though it's incorrect.
519 				**
520 				** C99 says that %z must be replaced by the
521 				** empty string if the time zone is not
522 				** determinable, so output nothing if the
523 				** appropriate variables are not available.
524 				*/
525 #ifndef STD_INSPIRED
526 				if (t->tm_isdst == 0)
527 #ifdef USG_COMPAT
528 					diff = -timezone;
529 #else /* !defined USG_COMPAT */
530 					continue;
531 #endif /* !defined USG_COMPAT */
532 				else
533 #ifdef ALTZONE
534 					diff = -altzone;
535 #else /* !defined ALTZONE */
536 					continue;
537 #endif /* !defined ALTZONE */
538 #else /* defined STD_INSPIRED */
539 				{
540 					struct tm tmp;
541 					time_t lct, gct;
542 
543 					/*
544 					** Get calendar time from t
545 					** being treated as local.
546 					*/
547 					tmp = *t; /* mktime discards const */
548 					lct = mktime(&tmp);
549 
550 					if (lct == (time_t)-1)
551 						continue;
552 
553 					/*
554 					** Get calendar time from t
555 					** being treated as GMT.
556 					**/
557 					tmp = *t; /* mktime discards const */
558 					gct = timegm(&tmp);
559 
560 					if (gct == (time_t)-1)
561 						continue;
562 
563 					/* LINTED difference will fit int */
564 					diff = (intmax_t)gct - (intmax_t)lct;
565 				}
566 #endif /* defined STD_INSPIRED */
567 #endif /* !defined TM_GMTOFF */
568 				if (diff < 0) {
569 					sign = "-";
570 					diff = -diff;
571 				} else	sign = "+";
572 				pt = _add(sign, pt, ptlim);
573 				diff /= SECSPERMIN;
574 				diff = (diff / MINSPERHOUR) * 100 +
575 					(diff % MINSPERHOUR);
576 				_DIAGASSERT(__type_fit(int, diff));
577 				pt = _conv((int)diff, "%04d", pt, ptlim);
578 				}
579 				continue;
580 #if 0
581 			case '+':
582 				pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
583 				    pt, ptlim, warnp, loc);
584 				continue;
585 #endif
586 			case '%':
587 			/*
588 			** X311J/88-090 (4.12.3.5): if conversion char is
589 			** undefined, behavior is undefined. Print out the
590 			** character itself as printf(3) also does.
591 			*/
592 			default:
593 				break;
594 			}
595 		}
596 		if (pt == ptlim)
597 			break;
598 		*pt++ = *format;
599 	}
600 	return pt;
601 }
602 
603 size_t
604 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
605 {
606 	tzset();
607 	return strftime_z(NULL, s, maxsize, format, t);
608 }
609 
610 size_t
611 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
612     const struct tm * __restrict t, locale_t loc)
613 {
614 	tzset();
615 	return strftime_lz(NULL, s, maxsize, format, t, loc);
616 }
617 
618 static char *
619 _conv(int n, const char *format, char *pt, const char *ptlim)
620 {
621 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
622 
623 	(void) snprintf(buf, sizeof(buf), format, n);
624 	return _add(buf, pt, ptlim);
625 }
626 
627 static char *
628 _add(const char *str, char *pt, const char *ptlim)
629 {
630 	while (pt < ptlim && (*pt = *str++) != '\0')
631 		++pt;
632 	return pt;
633 }
634 
635 /*
636 ** POSIX and the C Standard are unclear or inconsistent about
637 ** what %C and %y do if the year is negative or exceeds 9999.
638 ** Use the convention that %C concatenated with %y yields the
639 ** same output as %Y, and that %Y contains at least 4 bytes,
640 ** with more only if necessary.
641 */
642 
643 static char *
644 _yconv(int a, int b, bool convert_top, bool convert_yy,
645     char *pt, const char * ptlim)
646 {
647 	int	lead;
648 	int	trail;
649 
650 #define DIVISOR	100
651 	trail = a % DIVISOR + b % DIVISOR;
652 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
653 	trail %= DIVISOR;
654 	if (trail < 0 && lead > 0) {
655 		trail += DIVISOR;
656 		--lead;
657 	} else if (lead < 0 && trail > 0) {
658 		trail -= DIVISOR;
659 		++lead;
660 	}
661 	if (convert_top) {
662 		if (lead == 0 && trail < 0)
663 			pt = _add("-0", pt, ptlim);
664 		else	pt = _conv(lead, "%02d", pt, ptlim);
665 	}
666 	if (convert_yy)
667 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
668 	return pt;
669 }
670