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