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