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