Home | History | Annotate | Line # | Download | only in time
strftime.c revision 1.38.2.1
      1 /*	$NetBSD: strftime.c,v 1.38.2.1 2017/04/21 16:53:09 bouyer Exp $	*/
      2 
      3 /* Convert a broken-down timestamp to a string.  */
      4 
      5 /* Copyright 1989 The Regents of the University of California.
      6    All rights reserved.
      7 
      8    Redistribution and use in source and binary forms, with or without
      9    modification, are permitted provided that the following conditions
     10    are met:
     11    1. Redistributions of source code must retain the above copyright
     12       notice, this list of conditions and the following disclaimer.
     13    2. Redistributions in binary form must reproduce the above copyright
     14       notice, this list of conditions and the following disclaimer in the
     15       documentation and/or other materials provided with the distribution.
     16    3. Neither the name of the University nor the names of its contributors
     17       may be used to endorse or promote products derived from this software
     18       without specific prior written permission.
     19 
     20    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
     21    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23    ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     24    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     25    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     26    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     27    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     28    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     29    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     30    SUCH DAMAGE.  */
     31 
     32 #include <sys/cdefs.h>
     33 #if defined(LIBC_SCCS) && !defined(lint)
     34 #if 0
     35 static char	elsieid[] = "@(#)strftime.c	7.64";
     36 static char	elsieid[] = "@(#)strftime.c	8.3";
     37 #else
     38 __RCSID("$NetBSD: strftime.c,v 1.38.2.1 2017/04/21 16:53:09 bouyer Exp $");
     39 #endif
     40 #endif /* LIBC_SCCS and not lint */
     41 
     42 #include "namespace.h"
     43 
     44 #include <stddef.h>
     45 #include <assert.h>
     46 #include <locale.h>
     47 #include "setlocale_local.h"
     48 
     49 /*
     50 ** Based on the UCB version with the copyright notice appearing above.
     51 **
     52 ** This is ANSIish only when "multibyte character == plain character".
     53 */
     54 
     55 #include "private.h"
     56 
     57 /*
     58 ** We don't use these extensions in strftime operation even when
     59 ** supported by the local tzcode configuration.  A strictly
     60 ** conforming C application may leave them in undefined state.
     61 */
     62 
     63 #ifdef _LIBC
     64 #undef TM_ZONE
     65 #undef TM_GMTOFF
     66 #endif
     67 
     68 #include <fcntl.h>
     69 #include <locale.h>
     70 
     71 #ifdef __weak_alias
     72 __weak_alias(strftime_l, _strftime_l)
     73 __weak_alias(strftime_lz, _strftime_lz)
     74 __weak_alias(strftime_z, _strftime_z)
     75 #endif
     76 
     77 #include "sys/localedef.h"
     78 #define _TIME_LOCALE(loc) \
     79     ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME]))
     80 #define c_fmt   d_t_fmt
     81 
     82 static char *	_add(const char *, char *, const char *);
     83 static char *	_conv(int, const char *, char *, const char *);
     84 static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
     85 			const char *, int *, locale_t);
     86 static char *	_yconv(int, int, bool, bool, char *, const char *);
     87 
     88 #if !HAVE_POSIX_DECLS
     89 extern char *	tzname[];
     90 #endif
     91 
     92 #ifndef YEAR_2000_NAME
     93 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
     94 #endif /* !defined YEAR_2000_NAME */
     95 
     96 #define IN_NONE	0
     97 #define IN_SOME	1
     98 #define IN_THIS	2
     99 #define IN_ALL	3
    100 
    101 size_t
    102 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
    103     const char * __restrict format, const struct tm * __restrict t)
    104 {
    105 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
    106 }
    107 
    108 #if HAVE_STRFTIME_L
    109 size_t
    110 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
    111 	   locale_t locale)
    112 {
    113   /* Just call strftime, as only the C locale is supported.  */
    114   return strftime(s, maxsize, format, t);
    115 }
    116 #endif
    117 
    118 size_t
    119 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
    120     const char *const format, const struct tm *const t, locale_t loc)
    121 {
    122 	char *	p;
    123 	int	warn;
    124 
    125 	warn = IN_NONE;
    126 	p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
    127 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
    128 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
    129 		(void) fprintf(stderr, "\n");
    130 		(void) fprintf(stderr, "strftime format \"%s\" ", format);
    131 		(void) fprintf(stderr, "yields only two digits of years in ");
    132 		if (warn == IN_SOME)
    133 			(void) fprintf(stderr, "some locales");
    134 		else if (warn == IN_THIS)
    135 			(void) fprintf(stderr, "the current locale");
    136 		else	(void) fprintf(stderr, "all locales");
    137 		(void) fprintf(stderr, "\n");
    138 	}
    139 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
    140 	if (p == s + maxsize)
    141 		return 0;
    142 	*p = '\0';
    143 	return p - s;
    144 }
    145 
    146 static char *
    147 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
    148      const char *ptlim, int *warnp, locale_t loc)
    149 {
    150 	for ( ; *format; ++format) {
    151 		if (*format == '%') {
    152 label:
    153 			switch (*++format) {
    154 			case '\0':
    155 				--format;
    156 				break;
    157 			case 'A':
    158 				pt = _add((t->tm_wday < 0 ||
    159 					t->tm_wday >= DAYSPERWEEK) ?
    160 					"?" : _TIME_LOCALE(loc)->day[t->tm_wday],
    161 					pt, ptlim);
    162 				continue;
    163 			case 'a':
    164 				pt = _add((t->tm_wday < 0 ||
    165 					t->tm_wday >= DAYSPERWEEK) ?
    166 					"?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
    167 					pt, ptlim);
    168 				continue;
    169 			case 'B':
    170 				pt = _add((t->tm_mon < 0 ||
    171 					t->tm_mon >= MONSPERYEAR) ?
    172 					"?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
    173 					pt, ptlim);
    174 				continue;
    175 			case 'b':
    176 			case 'h':
    177 				pt = _add((t->tm_mon < 0 ||
    178 					t->tm_mon >= MONSPERYEAR) ?
    179 					"?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
    180 					pt, ptlim);
    181 				continue;
    182 			case 'C':
    183 				/*
    184 				** %C used to do a...
    185 				**	_fmt("%a %b %e %X %Y", t);
    186 				** ...whereas now POSIX 1003.2 calls for
    187 				** something completely different.
    188 				** (ado, 1993-05-24)
    189 				*/
    190 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    191 					    true, false, pt, ptlim);
    192 				continue;
    193 			case 'c':
    194 				{
    195 				int warn2 = IN_SOME;
    196 
    197 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
    198 				    ptlim, &warn2, loc);
    199 				if (warn2 == IN_ALL)
    200 					warn2 = IN_THIS;
    201 				if (warn2 > *warnp)
    202 					*warnp = warn2;
    203 				}
    204 				continue;
    205 			case 'D':
    206 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
    207 				    loc);
    208 				continue;
    209 			case 'd':
    210 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
    211 				continue;
    212 			case 'E':
    213 			case 'O':
    214 				/*
    215 				** C99 locale modifiers.
    216 				** The sequences
    217 				**	%Ec %EC %Ex %EX %Ey %EY
    218 				**	%Od %oe %OH %OI %Om %OM
    219 				**	%OS %Ou %OU %OV %Ow %OW %Oy
    220 				** are supposed to provide alternate
    221 				** representations.
    222 				*/
    223 				goto label;
    224 			case 'e':
    225 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
    226 				continue;
    227 			case 'F':
    228 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
    229 				    loc);
    230 				continue;
    231 			case 'H':
    232 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
    233 				continue;
    234 			case 'I':
    235 				pt = _conv((t->tm_hour % 12) ?
    236 					(t->tm_hour % 12) : 12,
    237 					"%02d", pt, ptlim);
    238 				continue;
    239 			case 'j':
    240 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
    241 				continue;
    242 			case 'k':
    243 				/*
    244 				** This used to be...
    245 				**	_conv(t->tm_hour % 12 ?
    246 				**		t->tm_hour % 12 : 12, 2, ' ');
    247 				** ...and has been changed to the below to
    248 				** match SunOS 4.1.1 and Arnold Robbins'
    249 				** strftime version 3.0. That is, "%k" and
    250 				** "%l" have been swapped.
    251 				** (ado, 1993-05-24)
    252 				*/
    253 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
    254 				continue;
    255 #ifdef KITCHEN_SINK
    256 			case 'K':
    257 				/*
    258 				** After all this time, still unclaimed!
    259 				*/
    260 				pt = _add("kitchen sink", pt, ptlim);
    261 				continue;
    262 #endif /* defined KITCHEN_SINK */
    263 			case 'l':
    264 				/*
    265 				** This used to be...
    266 				**	_conv(t->tm_hour, 2, ' ');
    267 				** ...and has been changed to the below to
    268 				** match SunOS 4.1.1 and Arnold Robbin's
    269 				** strftime version 3.0. That is, "%k" and
    270 				** "%l" have been swapped.
    271 				** (ado, 1993-05-24)
    272 				*/
    273 				pt = _conv((t->tm_hour % 12) ?
    274 					(t->tm_hour % 12) : 12,
    275 					"%2d", pt, ptlim);
    276 				continue;
    277 			case 'M':
    278 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
    279 				continue;
    280 			case 'm':
    281 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
    282 				continue;
    283 			case 'n':
    284 				pt = _add("\n", pt, ptlim);
    285 				continue;
    286 			case 'p':
    287 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
    288 					_TIME_LOCALE(loc)->am_pm[1] :
    289 					_TIME_LOCALE(loc)->am_pm[0],
    290 					pt, ptlim);
    291 				continue;
    292 			case 'R':
    293 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
    294 				    loc);
    295 				continue;
    296 			case 'r':
    297 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
    298 				    pt, ptlim, warnp, loc);
    299 				continue;
    300 			case 'S':
    301 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
    302 				continue;
    303 			case 's':
    304 				{
    305 					struct tm	tm;
    306 					char		buf[INT_STRLEN_MAXIMUM(
    307 								time_t) + 1];
    308 					time_t		mkt;
    309 
    310 					tm = *t;
    311 					mkt = mktime(&tm);
    312 					/* CONSTCOND */
    313 					if (TYPE_SIGNED(time_t))
    314 						(void)snprintf(buf, sizeof(buf),
    315 						    "%jd", (intmax_t) mkt);
    316 					else	(void)snprintf(buf, sizeof(buf),
    317 						    "%ju", (uintmax_t) mkt);
    318 					pt = _add(buf, pt, ptlim);
    319 				}
    320 				continue;
    321 			case 'T':
    322 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
    323 				    loc);
    324 				continue;
    325 			case 't':
    326 				pt = _add("\t", pt, ptlim);
    327 				continue;
    328 			case 'U':
    329 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    330 					t->tm_wday) / DAYSPERWEEK,
    331 					"%02d", pt, ptlim);
    332 				continue;
    333 			case 'u':
    334 				/*
    335 				** From Arnold Robbins' strftime version 3.0:
    336 				** "ISO 8601: Weekday as a decimal number
    337 				** [1 (Monday) - 7]"
    338 				** (ado, 1993-05-24)
    339 				*/
    340 				pt = _conv((t->tm_wday == 0) ?
    341 					DAYSPERWEEK : t->tm_wday,
    342 					"%d", pt, ptlim);
    343 				continue;
    344 			case 'V':	/* ISO 8601 week number */
    345 			case 'G':	/* ISO 8601 year (four digits) */
    346 			case 'g':	/* ISO 8601 year (two digits) */
    347 /*
    348 ** From Arnold Robbins' strftime version 3.0: "the week number of the
    349 ** year (the first Monday as the first day of week 1) as a decimal number
    350 ** (01-53)."
    351 ** (ado, 1993-05-24)
    352 **
    353 ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
    354 ** "Week 01 of a year is per definition the first week which has the
    355 ** Thursday in this year, which is equivalent to the week which contains
    356 ** the fourth day of January. In other words, the first week of a new year
    357 ** is the week which has the majority of its days in the new year. Week 01
    358 ** might also contain days from the previous year and the week before week
    359 ** 01 of a year is the last week (52 or 53) of the previous year even if
    360 ** it contains days from the new year. A week starts with Monday (day 1)
    361 ** and ends with Sunday (day 7). For example, the first week of the year
    362 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
    363 ** (ado, 1996-01-02)
    364 */
    365 				{
    366 					int	year;
    367 					int	base;
    368 					int	yday;
    369 					int	wday;
    370 					int	w;
    371 
    372 					year = t->tm_year;
    373 					base = TM_YEAR_BASE;
    374 					yday = t->tm_yday;
    375 					wday = t->tm_wday;
    376 					for ( ; ; ) {
    377 						int	len;
    378 						int	bot;
    379 						int	top;
    380 
    381 						len = isleap_sum(year, base) ?
    382 							DAYSPERLYEAR :
    383 							DAYSPERNYEAR;
    384 						/*
    385 						** What yday (-3 ... 3) does
    386 						** the ISO year begin on?
    387 						*/
    388 						bot = ((yday + 11 - wday) %
    389 							DAYSPERWEEK) - 3;
    390 						/*
    391 						** What yday does the NEXT
    392 						** ISO year begin on?
    393 						*/
    394 						top = bot -
    395 							(len % DAYSPERWEEK);
    396 						if (top < -3)
    397 							top += DAYSPERWEEK;
    398 						top += len;
    399 						if (yday >= top) {
    400 							++base;
    401 							w = 1;
    402 							break;
    403 						}
    404 						if (yday >= bot) {
    405 							w = 1 + ((yday - bot) /
    406 								DAYSPERWEEK);
    407 							break;
    408 						}
    409 						--base;
    410 						yday += isleap_sum(year, base) ?
    411 							DAYSPERLYEAR :
    412 							DAYSPERNYEAR;
    413 					}
    414 #ifdef XPG4_1994_04_09
    415 					if ((w == 52 &&
    416 						t->tm_mon == TM_JANUARY) ||
    417 						(w == 1 &&
    418 						t->tm_mon == TM_DECEMBER))
    419 							w = 53;
    420 #endif /* defined XPG4_1994_04_09 */
    421 					if (*format == 'V')
    422 						pt = _conv(w, "%02d",
    423 							pt, ptlim);
    424 					else if (*format == 'g') {
    425 						*warnp = IN_ALL;
    426 						pt = _yconv(year, base,
    427 							false, true,
    428 							pt, ptlim);
    429 					} else	pt = _yconv(year, base,
    430 							true, true,
    431 							pt, ptlim);
    432 				}
    433 				continue;
    434 			case 'v':
    435 				/*
    436 				** From Arnold Robbins' strftime version 3.0:
    437 				** "date as dd-bbb-YYYY"
    438 				** (ado, 1993-05-24)
    439 				*/
    440 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
    441 				    loc);
    442 				continue;
    443 			case 'W':
    444 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    445 					(t->tm_wday ?
    446 					(t->tm_wday - 1) :
    447 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
    448 					"%02d", pt, ptlim);
    449 				continue;
    450 			case 'w':
    451 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
    452 				continue;
    453 			case 'X':
    454 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
    455 				    ptlim, warnp, loc);
    456 				continue;
    457 			case 'x':
    458 				{
    459 				int	warn2 = IN_SOME;
    460 
    461 				pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
    462 				    ptlim, &warn2, loc);
    463 				if (warn2 == IN_ALL)
    464 					warn2 = IN_THIS;
    465 				if (warn2 > *warnp)
    466 					*warnp = warn2;
    467 				}
    468 				continue;
    469 			case 'y':
    470 				*warnp = IN_ALL;
    471 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    472 					false, true,
    473 					pt, ptlim);
    474 				continue;
    475 			case 'Y':
    476 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    477 					true, true,
    478 					pt, ptlim);
    479 				continue;
    480 			case 'Z':
    481 #ifdef TM_ZONE
    482 				pt = _add(t->TM_ZONE, pt, ptlim);
    483 #endif /* defined TM_ZONE */
    484 				if (t->tm_isdst >= 0)
    485 					pt = _add((sp ?
    486 					    tzgetname(sp, t->tm_isdst) :
    487 					    tzname[t->tm_isdst != 0]),
    488 					    pt, ptlim);
    489 				/*
    490 				** C99 says that %Z must be replaced by the
    491 				** empty string if the time zone is not
    492 				** determinable.
    493 				*/
    494 				continue;
    495 			case 'z':
    496 #if defined TM_GMTOFF || defined USG_COMPAT || defined ALTZONE
    497 				{
    498 				long		diff;
    499 				char const *	sign;
    500 				bool negative;
    501 
    502 				if (t->tm_isdst < 0)
    503 					continue;
    504 # ifdef TM_GMTOFF
    505 				diff = (int)t->TM_GMTOFF;
    506 # else
    507 				/*
    508 				** C99 says that the UT offset must
    509 				** be computed by looking only at
    510 				** tm_isdst. This requirement is
    511 				** incorrect, since it means the code
    512 				** must rely on magic (in this case
    513 				** altzone and timezone), and the
    514 				** magic might not have the correct
    515 				** offset. Doing things correctly is
    516 				** tricky and requires disobeying C99;
    517 				** see GNU C strftime for details.
    518 				** For now, punt and conform to the
    519 				** standard, even though it's incorrect.
    520 				**
    521 				** C99 says that %z must be replaced by the
    522 				** empty string if the time zone is not
    523 				** determinable, so output nothing if the
    524 				** appropriate variables are not available.
    525 				*/
    526 #  ifndef STD_INSPIRED
    527 				if (t->tm_isdst == 0)
    528 #   ifdef USG_COMPAT
    529 					diff = -timezone;
    530 #   else
    531 					continue;
    532 #   endif
    533 				else
    534 #   ifdef ALTZONE
    535 					diff = -altzone;
    536 #   else
    537 					continue;
    538 #   endif
    539 #  else
    540 				{
    541 					struct tm tmp;
    542 					time_t lct, gct;
    543 
    544 					/*
    545 					** Get calendar time from t
    546 					** being treated as local.
    547 					*/
    548 					tmp = *t; /* mktime discards const */
    549 					lct = mktime(&tmp);
    550 
    551 					if (lct == (time_t)-1)
    552 						continue;
    553 
    554 					/*
    555 					** Get calendar time from t
    556 					** being treated as GMT.
    557 					**/
    558 					tmp = *t; /* mktime discards const */
    559 					gct = timegm(&tmp);
    560 
    561 					if (gct == (time_t)-1)
    562 						continue;
    563 
    564 					/* LINTED difference will fit int */
    565 					diff = (intmax_t)gct - (intmax_t)lct;
    566 				}
    567 #  endif
    568 # endif
    569 				negative = diff < 0;
    570 				if (diff == 0) {
    571 #ifdef TM_ZONE
    572 				  negative = t->TM_ZONE[0] == '-';
    573 #else
    574 				  negative
    575 				    = (t->tm_isdst < 0
    576 				       || tzname[t->tm_isdst != 0][0] == '-');
    577 #endif
    578 				}
    579 				if (negative) {
    580 					sign = "-";
    581 					diff = -diff;
    582 				} else	sign = "+";
    583 				pt = _add(sign, pt, ptlim);
    584 				diff /= SECSPERMIN;
    585 				diff = (diff / MINSPERHOUR) * 100 +
    586 					(diff % MINSPERHOUR);
    587 				_DIAGASSERT(__type_fit(int, diff));
    588 				pt = _conv((int)diff, "%04d", pt, ptlim);
    589 				}
    590 #endif
    591 				continue;
    592 #if 0
    593 			case '+':
    594 				pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
    595 				    pt, ptlim, warnp, loc);
    596 				continue;
    597 #endif
    598 			case '%':
    599 			/*
    600 			** X311J/88-090 (4.12.3.5): if conversion char is
    601 			** undefined, behavior is undefined. Print out the
    602 			** character itself as printf(3) also does.
    603 			*/
    604 			default:
    605 				break;
    606 			}
    607 		}
    608 		if (pt == ptlim)
    609 			break;
    610 		*pt++ = *format;
    611 	}
    612 	return pt;
    613 }
    614 
    615 size_t
    616 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
    617 {
    618 	tzset();
    619 	return strftime_z(NULL, s, maxsize, format, t);
    620 }
    621 
    622 size_t
    623 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
    624     const struct tm * __restrict t, locale_t loc)
    625 {
    626 	tzset();
    627 	return strftime_lz(NULL, s, maxsize, format, t, loc);
    628 }
    629 
    630 static char *
    631 _conv(int n, const char *format, char *pt, const char *ptlim)
    632 {
    633 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
    634 
    635 	(void) snprintf(buf, sizeof(buf), format, n);
    636 	return _add(buf, pt, ptlim);
    637 }
    638 
    639 static char *
    640 _add(const char *str, char *pt, const char *ptlim)
    641 {
    642 	while (pt < ptlim && (*pt = *str++) != '\0')
    643 		++pt;
    644 	return pt;
    645 }
    646 
    647 /*
    648 ** POSIX and the C Standard are unclear or inconsistent about
    649 ** what %C and %y do if the year is negative or exceeds 9999.
    650 ** Use the convention that %C concatenated with %y yields the
    651 ** same output as %Y, and that %Y contains at least 4 bytes,
    652 ** with more only if necessary.
    653 */
    654 
    655 static char *
    656 _yconv(int a, int b, bool convert_top, bool convert_yy,
    657     char *pt, const char * ptlim)
    658 {
    659 	int	lead;
    660 	int	trail;
    661 
    662 #define DIVISOR	100
    663 	trail = a % DIVISOR + b % DIVISOR;
    664 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
    665 	trail %= DIVISOR;
    666 	if (trail < 0 && lead > 0) {
    667 		trail += DIVISOR;
    668 		--lead;
    669 	} else if (lead < 0 && trail > 0) {
    670 		trail -= DIVISOR;
    671 		++lead;
    672 	}
    673 	if (convert_top) {
    674 		if (lead == 0 && trail < 0)
    675 			pt = _add("-0", pt, ptlim);
    676 		else	pt = _conv(lead, "%02d", pt, ptlim);
    677 	}
    678 	if (convert_yy)
    679 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
    680 	return pt;
    681 }
    682