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