xref: /netbsd-src/lib/libc/time/strftime.c (revision d909946ca08dceb44d7d0f22ec9488679695d976)
1 /*	$NetBSD: strftime.c,v 1.36 2016/03/15 15:16:01 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.36 2016/03/15 15:16:01 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 == NULL) ? "%c" : format), t, s, s + maxsize,
128 	    &warn, loc);
129 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
130 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
131 		(void) fprintf(stderr, "\n");
132 		if (format == NULL)
133 			(void) fprintf(stderr, "NULL strftime format ");
134 		else	(void) fprintf(stderr, "strftime format \"%s\" ",
135 				format);
136 		(void) fprintf(stderr, "yields only two digits of years in ");
137 		if (warn == IN_SOME)
138 			(void) fprintf(stderr, "some locales");
139 		else if (warn == IN_THIS)
140 			(void) fprintf(stderr, "the current locale");
141 		else	(void) fprintf(stderr, "all locales");
142 		(void) fprintf(stderr, "\n");
143 	}
144 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
145 	if (p == s + maxsize)
146 		return 0;
147 	*p = '\0';
148 	return p - s;
149 }
150 
151 static char *
152 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
153      const char *ptlim, int *warnp, locale_t loc)
154 {
155 	for ( ; *format; ++format) {
156 		if (*format == '%') {
157 label:
158 			switch (*++format) {
159 			case '\0':
160 				--format;
161 				break;
162 			case 'A':
163 				pt = _add((t->tm_wday < 0 ||
164 					t->tm_wday >= DAYSPERWEEK) ?
165 					"?" : _TIME_LOCALE(loc)->day[t->tm_wday],
166 					pt, ptlim);
167 				continue;
168 			case 'a':
169 				pt = _add((t->tm_wday < 0 ||
170 					t->tm_wday >= DAYSPERWEEK) ?
171 					"?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
172 					pt, ptlim);
173 				continue;
174 			case 'B':
175 				pt = _add((t->tm_mon < 0 ||
176 					t->tm_mon >= MONSPERYEAR) ?
177 					"?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
178 					pt, ptlim);
179 				continue;
180 			case 'b':
181 			case 'h':
182 				pt = _add((t->tm_mon < 0 ||
183 					t->tm_mon >= MONSPERYEAR) ?
184 					"?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
185 					pt, ptlim);
186 				continue;
187 			case 'C':
188 				/*
189 				** %C used to do a...
190 				**	_fmt("%a %b %e %X %Y", t);
191 				** ...whereas now POSIX 1003.2 calls for
192 				** something completely different.
193 				** (ado, 1993-05-24)
194 				*/
195 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
196 					    true, false, pt, ptlim);
197 				continue;
198 			case 'c':
199 				{
200 				int warn2 = IN_SOME;
201 
202 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
203 				    ptlim, &warn2, loc);
204 				if (warn2 == IN_ALL)
205 					warn2 = IN_THIS;
206 				if (warn2 > *warnp)
207 					*warnp = warn2;
208 				}
209 				continue;
210 			case 'D':
211 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
212 				    loc);
213 				continue;
214 			case 'd':
215 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
216 				continue;
217 			case 'E':
218 			case 'O':
219 				/*
220 				** C99 locale modifiers.
221 				** The sequences
222 				**	%Ec %EC %Ex %EX %Ey %EY
223 				**	%Od %oe %OH %OI %Om %OM
224 				**	%OS %Ou %OU %OV %Ow %OW %Oy
225 				** are supposed to provide alternate
226 				** representations.
227 				*/
228 				goto label;
229 			case 'e':
230 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
231 				continue;
232 			case 'F':
233 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
234 				    loc);
235 				continue;
236 			case 'H':
237 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
238 				continue;
239 			case 'I':
240 				pt = _conv((t->tm_hour % 12) ?
241 					(t->tm_hour % 12) : 12,
242 					"%02d", pt, ptlim);
243 				continue;
244 			case 'j':
245 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
246 				continue;
247 			case 'k':
248 				/*
249 				** This used to be...
250 				**	_conv(t->tm_hour % 12 ?
251 				**		t->tm_hour % 12 : 12, 2, ' ');
252 				** ...and has been changed to the below to
253 				** match SunOS 4.1.1 and Arnold Robbins'
254 				** strftime version 3.0. That is, "%k" and
255 				** "%l" have been swapped.
256 				** (ado, 1993-05-24)
257 				*/
258 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
259 				continue;
260 #ifdef KITCHEN_SINK
261 			case 'K':
262 				/*
263 				** After all this time, still unclaimed!
264 				*/
265 				pt = _add("kitchen sink", pt, ptlim);
266 				continue;
267 #endif /* defined KITCHEN_SINK */
268 			case 'l':
269 				/*
270 				** This used to be...
271 				**	_conv(t->tm_hour, 2, ' ');
272 				** ...and has been changed to the below to
273 				** match SunOS 4.1.1 and Arnold Robbin's
274 				** strftime version 3.0. That is, "%k" and
275 				** "%l" have been swapped.
276 				** (ado, 1993-05-24)
277 				*/
278 				pt = _conv((t->tm_hour % 12) ?
279 					(t->tm_hour % 12) : 12,
280 					"%2d", pt, ptlim);
281 				continue;
282 			case 'M':
283 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
284 				continue;
285 			case 'm':
286 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
287 				continue;
288 			case 'n':
289 				pt = _add("\n", pt, ptlim);
290 				continue;
291 			case 'p':
292 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
293 					_TIME_LOCALE(loc)->am_pm[1] :
294 					_TIME_LOCALE(loc)->am_pm[0],
295 					pt, ptlim);
296 				continue;
297 			case 'R':
298 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
299 				    loc);
300 				continue;
301 			case 'r':
302 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
303 				    pt, ptlim, warnp, loc);
304 				continue;
305 			case 'S':
306 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
307 				continue;
308 			case 's':
309 				{
310 					struct tm	tm;
311 					char		buf[INT_STRLEN_MAXIMUM(
312 								time_t) + 1];
313 					time_t		mkt;
314 
315 					tm = *t;
316 					mkt = mktime(&tm);
317 					/* CONSTCOND */
318 					if (TYPE_SIGNED(time_t))
319 						(void)snprintf(buf, sizeof(buf),
320 						    "%jd", (intmax_t) mkt);
321 					else	(void)snprintf(buf, sizeof(buf),
322 						    "%ju", (uintmax_t) mkt);
323 					pt = _add(buf, pt, ptlim);
324 				}
325 				continue;
326 			case 'T':
327 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
328 				    loc);
329 				continue;
330 			case 't':
331 				pt = _add("\t", pt, ptlim);
332 				continue;
333 			case 'U':
334 				pt = _conv((t->tm_yday + DAYSPERWEEK -
335 					t->tm_wday) / DAYSPERWEEK,
336 					"%02d", pt, ptlim);
337 				continue;
338 			case 'u':
339 				/*
340 				** From Arnold Robbins' strftime version 3.0:
341 				** "ISO 8601: Weekday as a decimal number
342 				** [1 (Monday) - 7]"
343 				** (ado, 1993-05-24)
344 				*/
345 				pt = _conv((t->tm_wday == 0) ?
346 					DAYSPERWEEK : t->tm_wday,
347 					"%d", pt, ptlim);
348 				continue;
349 			case 'V':	/* ISO 8601 week number */
350 			case 'G':	/* ISO 8601 year (four digits) */
351 			case 'g':	/* ISO 8601 year (two digits) */
352 /*
353 ** From Arnold Robbins' strftime version 3.0: "the week number of the
354 ** year (the first Monday as the first day of week 1) as a decimal number
355 ** (01-53)."
356 ** (ado, 1993-05-24)
357 **
358 ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
359 ** "Week 01 of a year is per definition the first week which has the
360 ** Thursday in this year, which is equivalent to the week which contains
361 ** the fourth day of January. In other words, the first week of a new year
362 ** is the week which has the majority of its days in the new year. Week 01
363 ** might also contain days from the previous year and the week before week
364 ** 01 of a year is the last week (52 or 53) of the previous year even if
365 ** it contains days from the new year. A week starts with Monday (day 1)
366 ** and ends with Sunday (day 7). For example, the first week of the year
367 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
368 ** (ado, 1996-01-02)
369 */
370 				{
371 					int	year;
372 					int	base;
373 					int	yday;
374 					int	wday;
375 					int	w;
376 
377 					year = t->tm_year;
378 					base = TM_YEAR_BASE;
379 					yday = t->tm_yday;
380 					wday = t->tm_wday;
381 					for ( ; ; ) {
382 						int	len;
383 						int	bot;
384 						int	top;
385 
386 						len = isleap_sum(year, base) ?
387 							DAYSPERLYEAR :
388 							DAYSPERNYEAR;
389 						/*
390 						** What yday (-3 ... 3) does
391 						** the ISO year begin on?
392 						*/
393 						bot = ((yday + 11 - wday) %
394 							DAYSPERWEEK) - 3;
395 						/*
396 						** What yday does the NEXT
397 						** ISO year begin on?
398 						*/
399 						top = bot -
400 							(len % DAYSPERWEEK);
401 						if (top < -3)
402 							top += DAYSPERWEEK;
403 						top += len;
404 						if (yday >= top) {
405 							++base;
406 							w = 1;
407 							break;
408 						}
409 						if (yday >= bot) {
410 							w = 1 + ((yday - bot) /
411 								DAYSPERWEEK);
412 							break;
413 						}
414 						--base;
415 						yday += isleap_sum(year, base) ?
416 							DAYSPERLYEAR :
417 							DAYSPERNYEAR;
418 					}
419 #ifdef XPG4_1994_04_09
420 					if ((w == 52 &&
421 						t->tm_mon == TM_JANUARY) ||
422 						(w == 1 &&
423 						t->tm_mon == TM_DECEMBER))
424 							w = 53;
425 #endif /* defined XPG4_1994_04_09 */
426 					if (*format == 'V')
427 						pt = _conv(w, "%02d",
428 							pt, ptlim);
429 					else if (*format == 'g') {
430 						*warnp = IN_ALL;
431 						pt = _yconv(year, base,
432 							false, true,
433 							pt, ptlim);
434 					} else	pt = _yconv(year, base,
435 							true, true,
436 							pt, ptlim);
437 				}
438 				continue;
439 			case 'v':
440 				/*
441 				** From Arnold Robbins' strftime version 3.0:
442 				** "date as dd-bbb-YYYY"
443 				** (ado, 1993-05-24)
444 				*/
445 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
446 				    loc);
447 				continue;
448 			case 'W':
449 				pt = _conv((t->tm_yday + DAYSPERWEEK -
450 					(t->tm_wday ?
451 					(t->tm_wday - 1) :
452 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
453 					"%02d", pt, ptlim);
454 				continue;
455 			case 'w':
456 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
457 				continue;
458 			case 'X':
459 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
460 				    ptlim, warnp, loc);
461 				continue;
462 			case 'x':
463 				{
464 				int	warn2 = IN_SOME;
465 
466 				pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
467 				    ptlim, &warn2, loc);
468 				if (warn2 == IN_ALL)
469 					warn2 = IN_THIS;
470 				if (warn2 > *warnp)
471 					*warnp = warn2;
472 				}
473 				continue;
474 			case 'y':
475 				*warnp = IN_ALL;
476 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
477 					false, true,
478 					pt, ptlim);
479 				continue;
480 			case 'Y':
481 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
482 					true, true,
483 					pt, ptlim);
484 				continue;
485 			case 'Z':
486 #ifdef TM_ZONE
487 				pt = _add(t->TM_ZONE, pt, ptlim);
488 #endif /* defined TM_ZONE */
489 				if (t->tm_isdst >= 0)
490 					pt = _add((sp ?
491 					    tzgetname(sp, t->tm_isdst) :
492 					    tzname[t->tm_isdst != 0]),
493 					    pt, ptlim);
494 				/*
495 				** C99 says that %Z must be replaced by the
496 				** empty string if the time zone is not
497 				** determinable.
498 				*/
499 				continue;
500 			case 'z':
501 				{
502 				long		diff;
503 				char const *	sign;
504 
505 				if (t->tm_isdst < 0)
506 					continue;
507 #ifdef TM_GMTOFF
508 				diff = (int)t->TM_GMTOFF;
509 #else /* !defined TM_GMTOFF */
510 				/*
511 				** C99 says that the UT offset must
512 				** be computed by looking only at
513 				** tm_isdst. This requirement is
514 				** incorrect, since it means the code
515 				** must rely on magic (in this case
516 				** altzone and timezone), and the
517 				** magic might not have the correct
518 				** offset. Doing things correctly is
519 				** tricky and requires disobeying C99;
520 				** see GNU C strftime for details.
521 				** For now, punt and conform to the
522 				** standard, even though it's incorrect.
523 				**
524 				** C99 says that %z must be replaced by the
525 				** empty string if the time zone is not
526 				** determinable, so output nothing if the
527 				** appropriate variables are not available.
528 				*/
529 #ifndef STD_INSPIRED
530 				if (t->tm_isdst == 0)
531 #ifdef USG_COMPAT
532 					diff = -timezone;
533 #else /* !defined USG_COMPAT */
534 					continue;
535 #endif /* !defined USG_COMPAT */
536 				else
537 #ifdef ALTZONE
538 					diff = -altzone;
539 #else /* !defined ALTZONE */
540 					continue;
541 #endif /* !defined ALTZONE */
542 #else /* defined STD_INSPIRED */
543 				{
544 					struct tm tmp;
545 					time_t lct, gct;
546 
547 					/*
548 					** Get calendar time from t
549 					** being treated as local.
550 					*/
551 					tmp = *t; /* mktime discards const */
552 					lct = mktime(&tmp);
553 
554 					if (lct == (time_t)-1)
555 						continue;
556 
557 					/*
558 					** Get calendar time from t
559 					** being treated as GMT.
560 					**/
561 					tmp = *t; /* mktime discards const */
562 					gct = timegm(&tmp);
563 
564 					if (gct == (time_t)-1)
565 						continue;
566 
567 					/* LINTED difference will fit int */
568 					diff = (intmax_t)gct - (intmax_t)lct;
569 				}
570 #endif /* defined STD_INSPIRED */
571 #endif /* !defined TM_GMTOFF */
572 				if (diff < 0) {
573 					sign = "-";
574 					diff = -diff;
575 				} else	sign = "+";
576 				pt = _add(sign, pt, ptlim);
577 				diff /= SECSPERMIN;
578 				diff = (diff / MINSPERHOUR) * 100 +
579 					(diff % MINSPERHOUR);
580 				_DIAGASSERT(__type_fit(int, diff));
581 				pt = _conv((int)diff, "%04d", pt, ptlim);
582 				}
583 				continue;
584 #if 0
585 			case '+':
586 				pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
587 				    pt, ptlim, warnp, loc);
588 				continue;
589 #endif
590 			case '%':
591 			/*
592 			** X311J/88-090 (4.12.3.5): if conversion char is
593 			** undefined, behavior is undefined. Print out the
594 			** character itself as printf(3) also does.
595 			*/
596 			default:
597 				break;
598 			}
599 		}
600 		if (pt == ptlim)
601 			break;
602 		*pt++ = *format;
603 	}
604 	return pt;
605 }
606 
607 size_t
608 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
609 {
610 	tzset();
611 	return strftime_z(NULL, s, maxsize, format, t);
612 }
613 
614 size_t
615 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
616     const struct tm * __restrict t, locale_t loc)
617 {
618 	tzset();
619 	return strftime_lz(NULL, s, maxsize, format, t, loc);
620 }
621 
622 static char *
623 _conv(int n, const char *format, char *pt, const char *ptlim)
624 {
625 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
626 
627 	(void) snprintf(buf, sizeof(buf), format, n);
628 	return _add(buf, pt, ptlim);
629 }
630 
631 static char *
632 _add(const char *str, char *pt, const char *ptlim)
633 {
634 	while (pt < ptlim && (*pt = *str++) != '\0')
635 		++pt;
636 	return pt;
637 }
638 
639 /*
640 ** POSIX and the C Standard are unclear or inconsistent about
641 ** what %C and %y do if the year is negative or exceeds 9999.
642 ** Use the convention that %C concatenated with %y yields the
643 ** same output as %Y, and that %Y contains at least 4 bytes,
644 ** with more only if necessary.
645 */
646 
647 static char *
648 _yconv(int a, int b, bool convert_top, bool convert_yy,
649     char *pt, const char * ptlim)
650 {
651 	int	lead;
652 	int	trail;
653 
654 #define DIVISOR	100
655 	trail = a % DIVISOR + b % DIVISOR;
656 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
657 	trail %= DIVISOR;
658 	if (trail < 0 && lead > 0) {
659 		trail += DIVISOR;
660 		--lead;
661 	} else if (lead < 0 && trail > 0) {
662 		trail -= DIVISOR;
663 		++lead;
664 	}
665 	if (convert_top) {
666 		if (lead == 0 && trail < 0)
667 			pt = _add("-0", pt, ptlim);
668 		else	pt = _conv(lead, "%02d", pt, ptlim);
669 	}
670 	if (convert_yy)
671 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
672 	return pt;
673 }
674