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