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