tzstrftime.c revision 35237d135807af84bf9b0e5b8d7f8633e58db6f5
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				pt = _add((t->tm_mon < 0 ||
176					t->tm_mon >= MONSPERYEAR) ?
177					"?" : Locale->month[t->tm_mon],
178					pt, ptlim, modifier);
179				continue;
180			case 'b':
181			case 'h':
182				pt = _add((t->tm_mon < 0 ||
183					t->tm_mon >= MONSPERYEAR) ?
184					"?" : Locale->mon[t->tm_mon],
185					pt, ptlim, modifier);
186				continue;
187			case 'C':
188				/*
189				** %C used to do a...
190				**	_fmt("%a %b %e %X %Y", t);
191				** ...whereas now POSIX 1003.2 calls for
192				** something completely different.
193				** (ado, 1993-05-24)
194				*/
195				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
196					pt, ptlim, modifier);
197				continue;
198			case 'c':
199				{
200				int warn2 = IN_SOME;
201
202				pt = _fmt(Locale->c_fmt, t, pt, ptlim, warnp, Locale);
203				if (warn2 == IN_ALL)
204					warn2 = IN_THIS;
205				if (warn2 > *warnp)
206					*warnp = warn2;
207				}
208				continue;
209			case 'D':
210                                pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, Locale);
211				continue;
212			case 'd':
213                                pt = _conv(t->tm_mday,
214                                           getformat(modifier, "%02d",
215                                                     "%2d", "%d", "%02d"),
216                                           pt, ptlim);
217				continue;
218			case 'E':
219			case 'O':
220				/*
221				** C99 locale modifiers.
222				** The sequences
223				**	%Ec %EC %Ex %EX %Ey %EY
224				**	%Od %oe %OH %OI %Om %OM
225				**	%OS %Ou %OU %OV %Ow %OW %Oy
226				** are supposed to provide alternate
227				** representations.
228				*/
229				goto label;
230            case '_':
231            case '-':
232            case '0':
233            case '^':
234            case '#':
235                modifier = *format;
236                goto label;
237			case 'e':
238				pt = _conv(t->tm_mday,
239                                           getformat(modifier, "%2d",
240                                                     "%2d", "%d", "%02d"),
241                                           pt, ptlim);
242				continue;
243			case 'F':
244				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, Locale);
245				continue;
246			case 'H':
247				pt = _conv(t->tm_hour,
248                                           getformat(modifier, "%02d",
249                                                     "%2d", "%d", "%02d"),
250                                           pt, ptlim);
251				continue;
252			case 'I':
253				pt = _conv((t->tm_hour % 12) ?
254					(t->tm_hour % 12) : 12,
255					getformat(modifier, "%02d",
256                                                  "%2d", "%d", "%02d"),
257                                        pt, ptlim);
258				continue;
259			case 'j':
260				pt = _conv(t->tm_yday + 1,
261                           getformat(modifier, "%03d", "%3d", "%d", "%03d"),
262                           pt, ptlim);
263				continue;
264			case 'k':
265				/*
266				** This used to be...
267				**	_conv(t->tm_hour % 12 ?
268				**		t->tm_hour % 12 : 12, 2, ' ');
269				** ...and has been changed to the below to
270				** match SunOS 4.1.1 and Arnold Robbins'
271				** strftime version 3.0. That is, "%k" and
272				** "%l" have been swapped.
273				** (ado, 1993-05-24)
274				*/
275				pt = _conv(t->tm_hour,
276                                           getformat(modifier, "%2d",
277                                                     "%2d", "%d", "%02d"),
278                                           pt, ptlim);
279				continue;
280#ifdef KITCHEN_SINK
281			case 'K':
282				/*
283				** After all this time, still unclaimed!
284				*/
285				pt = _add("kitchen sink", pt, ptlim, modifier);
286				continue;
287#endif /* defined KITCHEN_SINK */
288			case 'l':
289				/*
290				** This used to be...
291				**	_conv(t->tm_hour, 2, ' ');
292				** ...and has been changed to the below to
293				** match SunOS 4.1.1 and Arnold Robbin's
294				** strftime version 3.0. That is, "%k" and
295				** "%l" have been swapped.
296				** (ado, 1993-05-24)
297				*/
298				pt = _conv((t->tm_hour % 12) ?
299					(t->tm_hour % 12) : 12,
300					getformat(modifier, "%2d",
301                                                  "%2d", "%d", "%02d"),
302                                        pt, ptlim);
303				continue;
304			case 'M':
305				pt = _conv(t->tm_min,
306                                           getformat(modifier, "%02d",
307                                                     "%2d", "%d", "%02d"),
308                                           pt, ptlim);
309				continue;
310			case 'm':
311				pt = _conv(t->tm_mon + 1,
312                                           getformat(modifier, "%02d",
313                                                     "%2d", "%d", "%02d"),
314                                           pt, ptlim);
315				continue;
316			case 'n':
317				pt = _add("\n", pt, ptlim, modifier);
318				continue;
319			case 'p':
320				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
321					Locale->pm :
322					Locale->am,
323					pt, ptlim, modifier);
324				continue;
325			case 'P':
326				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
327					Locale->pm :
328					Locale->am,
329					pt, ptlim, FORCE_LOWER_CASE);
330				continue;
331			case 'R':
332				pt = _fmt("%H:%M", t, pt, ptlim, warnp, Locale);
333				continue;
334			case 'r':
335				pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp, Locale);
336				continue;
337			case 'S':
338				pt = _conv(t->tm_sec,
339                                           getformat(modifier, "%02d",
340                                                     "%2d", "%d", "%02d"),
341                                           pt, ptlim);
342				continue;
343			case 's':
344				{
345					struct tm	tm;
346					char		buf[INT_STRLEN_MAXIMUM(
347								time_t) + 1];
348					time_t		mkt;
349
350					tm = *t;
351					mkt = mktime(&tm);
352					if (TYPE_SIGNED(time_t))
353						(void) sprintf(buf, "%ld",
354							(long) mkt);
355					else	(void) sprintf(buf, "%lu",
356							(unsigned long) mkt);
357					pt = _add(buf, pt, ptlim, modifier);
358				}
359				continue;
360			case 'T':
361				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, Locale);
362				continue;
363			case 't':
364				pt = _add("\t", pt, ptlim, modifier);
365				continue;
366			case 'U':
367				pt = _conv((t->tm_yday + DAYSPERWEEK -
368					t->tm_wday) / DAYSPERWEEK,
369					getformat(modifier, "%02d",
370                                                  "%2d", "%d", "%02d"),
371                                        pt, ptlim);
372				continue;
373			case 'u':
374				/*
375				** From Arnold Robbins' strftime version 3.0:
376				** "ISO 8601: Weekday as a decimal number
377				** [1 (Monday) - 7]"
378				** (ado, 1993-05-24)
379				*/
380				pt = _conv((t->tm_wday == 0) ?
381					DAYSPERWEEK : t->tm_wday, "%d", pt, ptlim);
382				continue;
383			case 'V':	/* ISO 8601 week number */
384			case 'G':	/* ISO 8601 year (four digits) */
385			case 'g':	/* ISO 8601 year (two digits) */
386/*
387** From Arnold Robbins' strftime version 3.0: "the week number of the
388** year (the first Monday as the first day of week 1) as a decimal number
389** (01-53)."
390** (ado, 1993-05-24)
391**
392** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
393** "Week 01 of a year is per definition the first week which has the
394** Thursday in this year, which is equivalent to the week which contains
395** the fourth day of January. In other words, the first week of a new year
396** is the week which has the majority of its days in the new year. Week 01
397** might also contain days from the previous year and the week before week
398** 01 of a year is the last week (52 or 53) of the previous year even if
399** it contains days from the new year. A week starts with Monday (day 1)
400** and ends with Sunday (day 7). For example, the first week of the year
401** 1997 lasts from 1996-12-30 to 1997-01-05..."
402** (ado, 1996-01-02)
403*/
404				{
405					int	year;
406					int	base;
407					int	yday;
408					int	wday;
409					int	w;
410
411					year = t->tm_year;
412					base = TM_YEAR_BASE;
413					yday = t->tm_yday;
414					wday = t->tm_wday;
415					for ( ; ; ) {
416						int	len;
417						int	bot;
418						int	top;
419
420						len = isleap_sum(year, base) ?
421							DAYSPERLYEAR :
422							DAYSPERNYEAR;
423						/*
424						** What yday (-3 ... 3) does
425						** the ISO year begin on?
426						*/
427						bot = ((yday + 11 - wday) %
428							DAYSPERWEEK) - 3;
429						/*
430						** What yday does the NEXT
431						** ISO year begin on?
432						*/
433						top = bot -
434							(len % DAYSPERWEEK);
435						if (top < -3)
436							top += DAYSPERWEEK;
437						top += len;
438						if (yday >= top) {
439							++base;
440							w = 1;
441							break;
442						}
443						if (yday >= bot) {
444							w = 1 + ((yday - bot) /
445								DAYSPERWEEK);
446							break;
447						}
448						--base;
449						yday += isleap_sum(year, base) ?
450							DAYSPERLYEAR :
451							DAYSPERNYEAR;
452					}
453#ifdef XPG4_1994_04_09
454					if ((w == 52 &&
455						t->tm_mon == TM_JANUARY) ||
456						(w == 1 &&
457						t->tm_mon == TM_DECEMBER))
458							w = 53;
459#endif /* defined XPG4_1994_04_09 */
460					if (*format == 'V')
461						pt = _conv(w,
462                                                           getformat(modifier,
463                                                                     "%02d",
464                                                                     "%2d",
465                                                                     "%d",
466                                                                     "%02d"),
467							   pt, ptlim);
468					else if (*format == 'g') {
469						*warnp = IN_ALL;
470						pt = _yconv(year, base, 0, 1,
471							pt, ptlim, modifier);
472					} else	pt = _yconv(year, base, 1, 1,
473							pt, ptlim, modifier);
474				}
475				continue;
476			case 'v':
477				/*
478				** From Arnold Robbins' strftime version 3.0:
479				** "date as dd-bbb-YYYY"
480				** (ado, 1993-05-24)
481				*/
482				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, Locale);
483				continue;
484			case 'W':
485				pt = _conv((t->tm_yday + DAYSPERWEEK -
486					(t->tm_wday ?
487					(t->tm_wday - 1) :
488					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
489					getformat(modifier, "%02d",
490                                                  "%2d", "%d", "%02d"),
491                                        pt, ptlim);
492				continue;
493			case 'w':
494				pt = _conv(t->tm_wday, "%d", pt, ptlim);
495				continue;
496			case 'X':
497				pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp, Locale);
498				continue;
499			case 'x':
500				{
501				int	warn2 = IN_SOME;
502
503				pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2, Locale);
504				if (warn2 == IN_ALL)
505					warn2 = IN_THIS;
506				if (warn2 > *warnp)
507					*warnp = warn2;
508				}
509				continue;
510			case 'y':
511				*warnp = IN_ALL;
512				pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
513					pt, ptlim, modifier);
514				continue;
515			case 'Y':
516				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
517					pt, ptlim, modifier);
518				continue;
519			case 'Z':
520#ifdef TM_ZONE
521				if (t->TM_ZONE != NULL)
522					pt = _add(t->TM_ZONE, pt, ptlim,
523                                                  modifier);
524				else
525#endif /* defined TM_ZONE */
526				if (t->tm_isdst >= 0)
527					pt = _add(tzname[t->tm_isdst != 0],
528						pt, ptlim, modifier);
529				/*
530				** C99 says that %Z must be replaced by the
531				** empty string if the time zone is not
532				** determinable.
533				*/
534				continue;
535			case 'z':
536				{
537				int		diff;
538				char const *	sign;
539
540				if (t->tm_isdst < 0)
541					continue;
542#ifdef TM_GMTOFF
543				diff = t->TM_GMTOFF;
544#else /* !defined TM_GMTOFF */
545				/*
546				** C99 says that the UTC offset must
547				** be computed by looking only at
548				** tm_isdst. This requirement is
549				** incorrect, since it means the code
550				** must rely on magic (in this case
551				** altzone and timezone), and the
552				** magic might not have the correct
553				** offset. Doing things correctly is
554				** tricky and requires disobeying C99;
555				** see GNU C strftime for details.
556				** For now, punt and conform to the
557				** standard, even though it's incorrect.
558				**
559				** C99 says that %z must be replaced by the
560				** empty string if the time zone is not
561				** determinable, so output nothing if the
562				** appropriate variables are not available.
563				*/
564				if (t->tm_isdst == 0)
565#ifdef USG_COMPAT
566					diff = -timezone;
567#else /* !defined USG_COMPAT */
568					continue;
569#endif /* !defined USG_COMPAT */
570				else
571#ifdef ALTZONE
572					diff = -altzone;
573#else /* !defined ALTZONE */
574					continue;
575#endif /* !defined ALTZONE */
576#endif /* !defined TM_GMTOFF */
577				if (diff < 0) {
578					sign = "-";
579					diff = -diff;
580				} else	sign = "+";
581				pt = _add(sign, pt, ptlim, modifier);
582				diff /= SECSPERMIN;
583				diff = (diff / MINSPERHOUR) * 100 +
584					(diff % MINSPERHOUR);
585				pt = _conv(diff,
586                                           getformat(modifier, "%04d",
587                                                     "%4d", "%d", "%04d"),
588                                           pt, ptlim);
589				}
590				continue;
591			case '+':
592				pt = _fmt(Locale->date_fmt, t, pt, ptlim,
593					warnp, Locale);
594				continue;
595			case '%':
596			/*
597			** X311J/88-090 (4.12.3.5): if conversion char is
598			** undefined, behavior is undefined. Print out the
599			** character itself as printf(3) also does.
600			*/
601			default:
602				break;
603			}
604		}
605		if (pt == ptlim)
606			break;
607		*pt++ = *format;
608	}
609	return pt;
610}
611
612static char *
613_conv(n, format, pt, ptlim)
614const int		n;
615const char * const	format;
616char * const		pt;
617const char * const	ptlim;
618{
619	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
620
621	(void) sprintf(buf, format, n);
622	return _add(buf, pt, ptlim, 0);
623}
624
625static char *
626_add(str, pt, ptlim, modifier)
627const char *		str;
628char *			pt;
629const char * const	ptlim;
630int                     modifier;
631{
632        int c;
633
634        switch (modifier) {
635        case FORCE_LOWER_CASE:
636                while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
637                        ++pt;
638                }
639                break;
640
641        case '^':
642                while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
643                        ++pt;
644                }
645                break;
646
647        case '#':
648                while (pt < ptlim && (c = *str++) != '\0') {
649                        if (isupper(c)) {
650                                c = tolower(c);
651                        } else if (islower(c)) {
652                                c = toupper(c);
653                        }
654                        *pt = c;
655                        ++pt;
656                }
657
658                break;
659
660        default:
661                while (pt < ptlim && (*pt = *str++) != '\0') {
662                        ++pt;
663                }
664        }
665
666	return pt;
667}
668
669/*
670** POSIX and the C Standard are unclear or inconsistent about
671** what %C and %y do if the year is negative or exceeds 9999.
672** Use the convention that %C concatenated with %y yields the
673** same output as %Y, and that %Y contains at least 4 bytes,
674** with more only if necessary.
675*/
676
677static char *
678_yconv(a, b, convert_top, convert_yy, pt, ptlim, modifier)
679const int		a;
680const int		b;
681const int		convert_top;
682const int		convert_yy;
683char *			pt;
684const char * const	ptlim;
685int                     modifier;
686{
687	register int	lead;
688	register int	trail;
689
690#define DIVISOR	100
691	trail = a % DIVISOR + b % DIVISOR;
692	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
693	trail %= DIVISOR;
694	if (trail < 0 && lead > 0) {
695		trail += DIVISOR;
696		--lead;
697	} else if (lead < 0 && trail > 0) {
698		trail -= DIVISOR;
699		++lead;
700	}
701	if (convert_top) {
702		if (lead == 0 && trail < 0)
703			pt = _add("-0", pt, ptlim, modifier);
704		else	pt = _conv(lead, getformat(modifier, "%02d",
705                                                   "%2d", "%d", "%02d"),
706                                   pt, ptlim);
707	}
708	if (convert_yy)
709		pt = _conv(((trail < 0) ? -trail : trail),
710                           getformat(modifier, "%02d", "%2d", "%d", "%02d"),
711                           pt, ptlim);
712	return pt;
713}
714
715#ifdef LOCALE_HOME
716static struct lc_time_T *
717_loc P((void))
718{
719	static const char	locale_home[] = LOCALE_HOME;
720	static const char	lc_time[] = "LC_TIME";
721	static char *		locale_buf;
722
723	int			fd;
724	int			oldsun;	/* "...ain't got nothin' to do..." */
725	char *			lbuf;
726	char *			name;
727	char *			p;
728	const char **		ap;
729	const char *		plim;
730	char			filename[FILENAME_MAX];
731	struct stat		st;
732	size_t			namesize;
733	size_t			bufsize;
734
735	/*
736	** Use localebuf.mon[0] to signal whether locale is already set up.
737	*/
738	if (localebuf.mon[0])
739		return &localebuf;
740	name = setlocale(LC_TIME, (char *) NULL);
741	if (name == NULL || *name == '\0')
742		goto no_locale;
743	/*
744	** If the locale name is the same as our cache, use the cache.
745	*/
746	lbuf = locale_buf;
747	if (lbuf != NULL && strcmp(name, lbuf) == 0) {
748		p = lbuf;
749		for (ap = (const char **) &localebuf;
750			ap < (const char **) (&localebuf + 1);
751				++ap)
752					*ap = p += strlen(p) + 1;
753		return &localebuf;
754	}
755	/*
756	** Slurp the locale file into the cache.
757	*/
758	namesize = strlen(name) + 1;
759	if (sizeof filename <
760		((sizeof locale_home) + namesize + (sizeof lc_time)))
761			goto no_locale;
762	oldsun = 0;
763	(void) sprintf(filename, "%s/%s/%s", locale_home, name, lc_time);
764	fd = open(filename, O_RDONLY);
765	if (fd < 0) {
766		/*
767		** Old Sun systems have a different naming and data convention.
768		*/
769		oldsun = 1;
770		(void) sprintf(filename, "%s/%s/%s", locale_home,
771			lc_time, name);
772		fd = open(filename, O_RDONLY);
773		if (fd < 0)
774			goto no_locale;
775	}
776	if (fstat(fd, &st) != 0)
777		goto bad_locale;
778	if (st.st_size <= 0)
779		goto bad_locale;
780	bufsize = namesize + st.st_size;
781	locale_buf = NULL;
782	lbuf = (lbuf == NULL) ? malloc(bufsize) : realloc(lbuf, bufsize);
783	if (lbuf == NULL)
784		goto bad_locale;
785	(void) strcpy(lbuf, name);
786	p = lbuf + namesize;
787	plim = p + st.st_size;
788	if (read(fd, p, (size_t) st.st_size) != st.st_size)
789		goto bad_lbuf;
790	if (close(fd) != 0)
791		goto bad_lbuf;
792	/*
793	** Parse the locale file into localebuf.
794	*/
795	if (plim[-1] != '\n')
796		goto bad_lbuf;
797	for (ap = (const char **) &localebuf;
798		ap < (const char **) (&localebuf + 1);
799			++ap) {
800				if (p == plim)
801					goto bad_lbuf;
802				*ap = p;
803				while (*p != '\n')
804					++p;
805				*p++ = '\0';
806	}
807	if (oldsun) {
808		/*
809		** SunOS 4 used an obsolescent format; see localdtconv(3).
810		** c_fmt had the ``short format for dates and times together''
811		** (SunOS 4 date, "%a %b %e %T %Z %Y" in the C locale);
812		** date_fmt had the ``long format for dates''
813		** (SunOS 4 strftime %C, "%A, %B %e, %Y" in the C locale).
814		** Discard the latter in favor of the former.
815		*/
816		localebuf.date_fmt = localebuf.c_fmt;
817	}
818	/*
819	** Record the successful parse in the cache.
820	*/
821	locale_buf = lbuf;
822
823	return &localebuf;
824
825bad_lbuf:
826	free(lbuf);
827bad_locale:
828	(void) close(fd);
829no_locale:
830	localebuf = C_time_locale;
831	locale_buf = NULL;
832	return &localebuf;
833}
834#endif /* defined LOCALE_HOME */
835