Home | History | Annotate | Download | only in libcutils
      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 <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
     35 static 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 
     43 static char *	_add P((const char *, char *, const char *, int));
     44 static char *	_conv P((int, const char *, char *, const char *));
     45 static char *	_fmt P((const char *, const struct tm *, char *, const char *,
     46 			int *, const struct strftime_locale *Locale));
     47 static char *	_yconv P((int, int, int, int, char *, const char *, int));
     48 static char *	getformat P((int, char *, char *, char *, char *));
     49 
     50 extern 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 
     94 size_t
     95 strftime_tz(s, maxsize, format, t, Locale)
     96 char * const		s;
     97 const size_t		maxsize;
     98 const char * const	format;
     99 const struct tm * const	t;
    100 const 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 
    129 static 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 
    145 static char *
    146 _fmt(format, t, pt, ptlim, warnp, Locale)
    147 const char *		format;
    148 const struct tm * const	t;
    149 char *			pt;
    150 const char * const	ptlim;
    151 int *			warnp;
    152 const struct strftime_locale *Locale;
    153 {
    154 	for ( ; *format; ++format) {
    155 		if (*format == '%') {
    156             int modifier = 0;
    157 label:
    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 
    619 static char *
    620 _conv(n, format, pt, ptlim)
    621 const int		n;
    622 const char * const	format;
    623 char * const		pt;
    624 const 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 
    632 static char *
    633 _add(str, pt, ptlim, modifier)
    634 const char *		str;
    635 char *			pt;
    636 const char * const	ptlim;
    637 int                     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 
    684 static char *
    685 _yconv(a, b, convert_top, convert_yy, pt, ptlim, modifier)
    686 const int		a;
    687 const int		b;
    688 const int		convert_top;
    689 const int		convert_yy;
    690 char *			pt;
    691 const char * const	ptlim;
    692 int                     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
    723 static 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 
    832 bad_lbuf:
    833 	free(lbuf);
    834 bad_locale:
    835 	(void) close(fd);
    836 no_locale:
    837 	localebuf = C_time_locale;
    838 	locale_buf = NULL;
    839 	return &localebuf;
    840 }
    841 #endif /* defined LOCALE_HOME */
    842