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