Home | History | Annotate | Line # | Download | only in time
strftime.c revision 1.42
      1 /*	$NetBSD: strftime.c,v 1.42 2018/10/19 23:05:35 christos 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.42 2018/10/19 23:05:35 christos 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 #include <stdio.h>
     71 
     72 #ifndef DEPRECATE_TWO_DIGIT_YEARS
     73 # define DEPRECATE_TWO_DIGIT_YEARS false
     74 #endif
     75 
     76 #ifdef __weak_alias
     77 __weak_alias(strftime_l, _strftime_l)
     78 __weak_alias(strftime_lz, _strftime_lz)
     79 __weak_alias(strftime_z, _strftime_z)
     80 #endif
     81 
     82 #include "sys/localedef.h"
     83 #define _TIME_LOCALE(loc) \
     84     ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME]))
     85 #define c_fmt   d_t_fmt
     86 
     87 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
     88 
     89 static char *	_add(const char *, char *, const char *);
     90 static char *	_conv(int, const char *, char *, const char *);
     91 static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
     92 			const char *, enum warn *, locale_t);
     93 static char *	_yconv(int, int, bool, bool, char *, const char *);
     94 
     95 #ifndef YEAR_2000_NAME
     96 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
     97 #endif /* !defined YEAR_2000_NAME */
     98 
     99 size_t
    100 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
    101     const char * __restrict format, const struct tm * __restrict t)
    102 {
    103 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
    104 }
    105 
    106 #if HAVE_STRFTIME_L
    107 size_t
    108 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
    109 	   locale_t locale)
    110 {
    111   /* Just call strftime, as only the C locale is supported.  */
    112   return strftime(s, maxsize, format, t);
    113 }
    114 #endif
    115 
    116 size_t
    117 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
    118     const char *const format, const struct tm *const t, locale_t loc)
    119 {
    120 	char *	p;
    121 	enum warn warn = IN_NONE;
    122 
    123 	p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
    124 	if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS
    125 	    && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
    126 		(void) fprintf(stderr, "\n");
    127 		(void) fprintf(stderr, "strftime format \"%s\" ", format);
    128 		(void) fprintf(stderr, "yields only two digits of years in ");
    129 		if (warn == IN_SOME)
    130 			(void) fprintf(stderr, "some locales");
    131 		else if (warn == IN_THIS)
    132 			(void) fprintf(stderr, "the current locale");
    133 		else	(void) fprintf(stderr, "all locales");
    134 		(void) fprintf(stderr, "\n");
    135 	}
    136 	if (p == s + maxsize)
    137 		return 0;
    138 	*p = '\0';
    139 	return p - s;
    140 }
    141 
    142 static char *
    143 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
    144      const char *ptlim, enum warn *warnp, locale_t loc)
    145 {
    146 	for ( ; *format; ++format) {
    147 		if (*format == '%') {
    148 label:
    149 			switch (*++format) {
    150 			case '\0':
    151 				--format;
    152 				break;
    153 			case 'A':
    154 				pt = _add((t->tm_wday < 0 ||
    155 					t->tm_wday >= DAYSPERWEEK) ?
    156 					"?" : _TIME_LOCALE(loc)->day[t->tm_wday],
    157 					pt, ptlim);
    158 				continue;
    159 			case 'a':
    160 				pt = _add((t->tm_wday < 0 ||
    161 					t->tm_wday >= DAYSPERWEEK) ?
    162 					"?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
    163 					pt, ptlim);
    164 				continue;
    165 			case 'B':
    166 				pt = _add((t->tm_mon < 0 ||
    167 					t->tm_mon >= MONSPERYEAR) ?
    168 					"?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
    169 					pt, ptlim);
    170 				continue;
    171 			case 'b':
    172 			case 'h':
    173 				pt = _add((t->tm_mon < 0 ||
    174 					t->tm_mon >= MONSPERYEAR) ?
    175 					"?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
    176 					pt, ptlim);
    177 				continue;
    178 			case 'C':
    179 				/*
    180 				** %C used to do a...
    181 				**	_fmt("%a %b %e %X %Y", t);
    182 				** ...whereas now POSIX 1003.2 calls for
    183 				** something completely different.
    184 				** (ado, 1993-05-24)
    185 				*/
    186 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    187 					    true, false, pt, ptlim);
    188 				continue;
    189 			case 'c':
    190 				{
    191 				enum warn warn2 = IN_SOME;
    192 
    193 				pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
    194 				    ptlim, &warn2, loc);
    195 				if (warn2 == IN_ALL)
    196 					warn2 = IN_THIS;
    197 				if (warn2 > *warnp)
    198 					*warnp = warn2;
    199 				}
    200 				continue;
    201 			case 'D':
    202 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
    203 				    loc);
    204 				continue;
    205 			case 'd':
    206 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
    207 				continue;
    208 			case 'E':
    209 			case 'O':
    210 				/*
    211 				** Locale modifiers of C99 and later.
    212 				** The sequences
    213 				**	%Ec %EC %Ex %EX %Ey %EY
    214 				**	%Od %oe %OH %OI %Om %OM
    215 				**	%OS %Ou %OU %OV %Ow %OW %Oy
    216 				** are supposed to provide alternative
    217 				** representations.
    218 				*/
    219 				goto label;
    220 			case 'e':
    221 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
    222 				continue;
    223 			case 'F':
    224 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
    225 				    loc);
    226 				continue;
    227 			case 'H':
    228 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
    229 				continue;
    230 			case 'I':
    231 				pt = _conv((t->tm_hour % 12) ?
    232 					(t->tm_hour % 12) : 12,
    233 					"%02d", pt, ptlim);
    234 				continue;
    235 			case 'j':
    236 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
    237 				continue;
    238 			case 'k':
    239 				/*
    240 				** This used to be...
    241 				**	_conv(t->tm_hour % 12 ?
    242 				**		t->tm_hour % 12 : 12, 2, ' ');
    243 				** ...and has been changed to the below to
    244 				** match SunOS 4.1.1 and Arnold Robbins'
    245 				** strftime version 3.0. That is, "%k" and
    246 				** "%l" have been swapped.
    247 				** (ado, 1993-05-24)
    248 				*/
    249 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
    250 				continue;
    251 #ifdef KITCHEN_SINK
    252 			case 'K':
    253 				/*
    254 				** After all this time, still unclaimed!
    255 				*/
    256 				pt = _add("kitchen sink", pt, ptlim);
    257 				continue;
    258 #endif /* defined KITCHEN_SINK */
    259 			case 'l':
    260 				/*
    261 				** This used to be...
    262 				**	_conv(t->tm_hour, 2, ' ');
    263 				** ...and has been changed to the below to
    264 				** match SunOS 4.1.1 and Arnold Robbin's
    265 				** strftime version 3.0. That is, "%k" and
    266 				** "%l" have been swapped.
    267 				** (ado, 1993-05-24)
    268 				*/
    269 				pt = _conv((t->tm_hour % 12) ?
    270 					(t->tm_hour % 12) : 12,
    271 					"%2d", pt, ptlim);
    272 				continue;
    273 			case 'M':
    274 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
    275 				continue;
    276 			case 'm':
    277 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
    278 				continue;
    279 			case 'n':
    280 				pt = _add("\n", pt, ptlim);
    281 				continue;
    282 			case 'p':
    283 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
    284 					_TIME_LOCALE(loc)->am_pm[1] :
    285 					_TIME_LOCALE(loc)->am_pm[0],
    286 					pt, ptlim);
    287 				continue;
    288 			case 'R':
    289 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
    290 				    loc);
    291 				continue;
    292 			case 'r':
    293 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
    294 				    pt, ptlim, warnp, loc);
    295 				continue;
    296 			case 'S':
    297 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
    298 				continue;
    299 			case 's':
    300 				{
    301 					struct tm	tm;
    302 					char		buf[INT_STRLEN_MAXIMUM(
    303 								time_t) + 1];
    304 					time_t		mkt;
    305 
    306 					tm = *t;
    307 					mkt = mktime(&tm);
    308 					/* CONSTCOND */
    309 					if (TYPE_SIGNED(time_t))
    310 						(void)snprintf(buf, sizeof(buf),
    311 						    "%jd", (intmax_t) mkt);
    312 					else	(void)snprintf(buf, sizeof(buf),
    313 						    "%ju", (uintmax_t) mkt);
    314 					pt = _add(buf, pt, ptlim);
    315 				}
    316 				continue;
    317 			case 'T':
    318 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
    319 				    loc);
    320 				continue;
    321 			case 't':
    322 				pt = _add("\t", pt, ptlim);
    323 				continue;
    324 			case 'U':
    325 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    326 					t->tm_wday) / DAYSPERWEEK,
    327 					"%02d", pt, ptlim);
    328 				continue;
    329 			case 'u':
    330 				/*
    331 				** From Arnold Robbins' strftime version 3.0:
    332 				** "ISO 8601: Weekday as a decimal number
    333 				** [1 (Monday) - 7]"
    334 				** (ado, 1993-05-24)
    335 				*/
    336 				pt = _conv((t->tm_wday == 0) ?
    337 					DAYSPERWEEK : t->tm_wday,
    338 					"%d", pt, ptlim);
    339 				continue;
    340 			case 'V':	/* ISO 8601 week number */
    341 			case 'G':	/* ISO 8601 year (four digits) */
    342 			case 'g':	/* ISO 8601 year (two digits) */
    343 /*
    344 ** From Arnold Robbins' strftime version 3.0: "the week number of the
    345 ** year (the first Monday as the first day of week 1) as a decimal number
    346 ** (01-53)."
    347 ** (ado, 1993-05-24)
    348 **
    349 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
    350 ** "Week 01 of a year is per definition the first week which has the
    351 ** Thursday in this year, which is equivalent to the week which contains
    352 ** the fourth day of January. In other words, the first week of a new year
    353 ** is the week which has the majority of its days in the new year. Week 01
    354 ** might also contain days from the previous year and the week before week
    355 ** 01 of a year is the last week (52 or 53) of the previous year even if
    356 ** it contains days from the new year. A week starts with Monday (day 1)
    357 ** and ends with Sunday (day 7). For example, the first week of the year
    358 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
    359 ** (ado, 1996-01-02)
    360 */
    361 				{
    362 					int	year;
    363 					int	base;
    364 					int	yday;
    365 					int	wday;
    366 					int	w;
    367 
    368 					year = t->tm_year;
    369 					base = TM_YEAR_BASE;
    370 					yday = t->tm_yday;
    371 					wday = t->tm_wday;
    372 					for ( ; ; ) {
    373 						int	len;
    374 						int	bot;
    375 						int	top;
    376 
    377 						len = isleap_sum(year, base) ?
    378 							DAYSPERLYEAR :
    379 							DAYSPERNYEAR;
    380 						/*
    381 						** What yday (-3 ... 3) does
    382 						** the ISO year begin on?
    383 						*/
    384 						bot = ((yday + 11 - wday) %
    385 							DAYSPERWEEK) - 3;
    386 						/*
    387 						** What yday does the NEXT
    388 						** ISO year begin on?
    389 						*/
    390 						top = bot -
    391 							(len % DAYSPERWEEK);
    392 						if (top < -3)
    393 							top += DAYSPERWEEK;
    394 						top += len;
    395 						if (yday >= top) {
    396 							++base;
    397 							w = 1;
    398 							break;
    399 						}
    400 						if (yday >= bot) {
    401 							w = 1 + ((yday - bot) /
    402 								DAYSPERWEEK);
    403 							break;
    404 						}
    405 						--base;
    406 						yday += isleap_sum(year, base) ?
    407 							DAYSPERLYEAR :
    408 							DAYSPERNYEAR;
    409 					}
    410 #ifdef XPG4_1994_04_09
    411 					if ((w == 52 &&
    412 						t->tm_mon == TM_JANUARY) ||
    413 						(w == 1 &&
    414 						t->tm_mon == TM_DECEMBER))
    415 							w = 53;
    416 #endif /* defined XPG4_1994_04_09 */
    417 					if (*format == 'V')
    418 						pt = _conv(w, "%02d",
    419 							pt, ptlim);
    420 					else if (*format == 'g') {
    421 						*warnp = IN_ALL;
    422 						pt = _yconv(year, base,
    423 							false, true,
    424 							pt, ptlim);
    425 					} else	pt = _yconv(year, base,
    426 							true, true,
    427 							pt, ptlim);
    428 				}
    429 				continue;
    430 			case 'v':
    431 				/*
    432 				** From Arnold Robbins' strftime version 3.0:
    433 				** "date as dd-bbb-YYYY"
    434 				** (ado, 1993-05-24)
    435 				*/
    436 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
    437 				    loc);
    438 				continue;
    439 			case 'W':
    440 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    441 					(t->tm_wday ?
    442 					(t->tm_wday - 1) :
    443 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
    444 					"%02d", pt, ptlim);
    445 				continue;
    446 			case 'w':
    447 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
    448 				continue;
    449 			case 'X':
    450 				pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
    451 				    ptlim, warnp, loc);
    452 				continue;
    453 			case 'x':
    454 				{
    455 				enum warn warn2 = IN_SOME;
    456 
    457 				pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
    458 				    ptlim, &warn2, loc);
    459 				if (warn2 == IN_ALL)
    460 					warn2 = IN_THIS;
    461 				if (warn2 > *warnp)
    462 					*warnp = warn2;
    463 				}
    464 				continue;
    465 			case 'y':
    466 				*warnp = IN_ALL;
    467 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    468 					false, true,
    469 					pt, ptlim);
    470 				continue;
    471 			case 'Y':
    472 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    473 					true, true,
    474 					pt, ptlim);
    475 				continue;
    476 			case 'Z':
    477 #ifdef TM_ZONE
    478 				pt = _add(t->TM_ZONE, pt, ptlim);
    479 #elif HAVE_TZNAME
    480 				if (t->tm_isdst >= 0)
    481 					pt = _add((sp ?
    482 					    tzgetname(sp, t->tm_isdst) :
    483 					    tzname[t->tm_isdst != 0]),
    484 					    pt, ptlim);
    485 #endif
    486 				/*
    487 				** C99 and later say that %Z must be
    488 				** replaced by the empty string if the
    489 				** time zone abbreviation is not
    490 				** determinable.
    491 				*/
    492 				continue;
    493 			case 'z':
    494 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE
    495 				{
    496 				long		diff;
    497 				char const *	sign;
    498 				bool negative;
    499 
    500 				if (t->tm_isdst < 0)
    501 					continue;
    502 # ifdef TM_GMTOFF
    503 				diff = (int)t->TM_GMTOFF;
    504 # else
    505 				/*
    506 				** C99 and later say that the UT offset must
    507 				** be computed by looking only at
    508 				** tm_isdst. This requirement is
    509 				** incorrect, since it means the code
    510 				** must rely on magic (in this case
    511 				** altzone and timezone), and the
    512 				** magic might not have the correct
    513 				** offset. Doing things correctly is
    514 				** tricky and requires disobeying the standard;
    515 				** see GNU C strftime for details.
    516 				** For now, punt and conform to the
    517 				** standard, even though it's incorrect.
    518 				**
    519 				** C99 and later say that %z must be replaced by
    520 				** the empty string if the time zone is not
    521 				** determinable, so output nothing if the
    522 				** appropriate variables are not available.
    523 				*/
    524 #  ifndef STD_INSPIRED
    525 				if (t->tm_isdst == 0)
    526 #   if USG_COMPAT
    527 					diff = -timezone;
    528 #   else
    529 					continue;
    530 #   endif
    531 				else
    532 #   ifdef ALTZONE
    533 					diff = -altzone;
    534 #   else
    535 					continue;
    536 #   endif
    537 #  else
    538 				{
    539 					struct tm tmp;
    540 					time_t lct, gct;
    541 
    542 					/*
    543 					** Get calendar time from t
    544 					** being treated as local.
    545 					*/
    546 					tmp = *t; /* mktime discards const */
    547 					lct = mktime(&tmp);
    548 
    549 					if (lct == (time_t)-1)
    550 						continue;
    551 
    552 					/*
    553 					** Get calendar time from t
    554 					** being treated as GMT.
    555 					**/
    556 					tmp = *t; /* mktime discards const */
    557 					gct = timegm(&tmp);
    558 
    559 					if (gct == (time_t)-1)
    560 						continue;
    561 
    562 					/* LINTED difference will fit int */
    563 					diff = (intmax_t)gct - (intmax_t)lct;
    564 				}
    565 #  endif
    566 # endif
    567 				negative = diff < 0;
    568 				if (diff == 0) {
    569 #ifdef TM_ZONE
    570 				  negative = t->TM_ZONE[0] == '-';
    571 #else
    572 				  negative = t->tm_isdst < 0;
    573 # if HAVE_TZNAME
    574 				  if (tzname[t->tm_isdst != 0][0] == '-')
    575 				    negative = true;
    576 # endif
    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