Home | History | Annotate | Line # | Download | only in time
      1 /*	$NetBSD: strftime.c,v 1.59 2026/01/09 16:11:23 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.59 2026/01/09 16:11:23 christos Exp $");
     39 #endif
     40 #endif /* LIBC_SCCS and not lint */
     41 
     42 #include "namespace.h"
     43 
     44 #include <stddef.h>
     45 #include <locale.h>
     46 #include "setlocale_local.h"
     47 
     48 /*
     49 ** Based on the UCB version with the copyright notice appearing above.
     50 **
     51 ** This is ANSIish only when "multibyte character == plain character".
     52 */
     53 
     54 #include "private.h"
     55 
     56 /*
     57 ** We don't use these extensions in strftime operation even when
     58 ** supported by the local tzcode configuration.  A strictly
     59 ** conforming C application may leave them in undefined state.
     60 */
     61 
     62 #ifdef _LIBC
     63 #undef TM_ZONE
     64 #undef TM_GMTOFF
     65 #endif
     66 
     67 #include <fcntl.h>
     68 #include <locale.h>
     69 #include <stdio.h>
     70 
     71 /* If true, the value returned by an idealized unlimited-range mktime
     72    always fits into an integer type with bounds MIN and MAX.
     73    If false, the value might not fit.
     74    This macro is usable in #if if its arguments are.
     75    Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file,
     76    divide by the maximum number of non-leap seconds in a year,
     77    divide again by two just to be safe,
     78    and account for the tm_year origin (1900) and time_t origin (1970).  */
     79 #define MKTIME_FITS_IN(min, max) \
     80   ((min) < 0 \
     81    && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \
     82    && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900)
     83 
     84 /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow
     85    or if it is not known whether mktime can fail,
     86    and is false if mktime definitely cannot fail.
     87    This macro is usable in #if, and so does not use TIME_T_MAX or sizeof.
     88    If the builder has not configured this macro, guess based on what
     89    known platforms do.  When in doubt, guess true.  */
     90 #ifndef MKTIME_MIGHT_OVERFLOW
     91 # if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
     92 #  include <sys/param.h>
     93 # endif
     94 # if ((/* The following heuristics assume native time_t.  */ \
     95        defined_time_tz) \
     96       || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \
     97 	      assume overflow unless we're on a known-safe host.  */ \
     98 	   !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \
     99 	  && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \
    100 		 if __TIMESIZE is 64.  */ \
    101 	      !defined __TIMESIZE || __TIMESIZE < 64) \
    102 	  && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \
    103 		 and later has 64-bit time_t on all platforms but i386 which \
    104 		 is currently scheduled for end-of-life on 2028-11-30.  */ \
    105 	      !defined __FreeBSD_version || __FreeBSD_version < 1200036 \
    106 	      || defined __i386) \
    107 	  && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t.  */ \
    108 	      !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \
    109 	  && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t.  */ \
    110 	      !defined OpenBSD || OpenBSD < 201405)))
    111 #  define MKTIME_MIGHT_OVERFLOW 1
    112 # else
    113 #  define MKTIME_MIGHT_OVERFLOW 0
    114 # endif
    115 #endif
    116 /* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range.  */
    117 static_assert(MKTIME_MIGHT_OVERFLOW
    118 	      || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX));
    119 
    120 #if MKTIME_MIGHT_OVERFLOW
    121 /* Do this after system includes as it redefines time_t, mktime, timeoff.  */
    122 # define USE_TIMEX_T true
    123 # include "localtime.c"
    124 #endif
    125 
    126 #ifndef DEPRECATE_TWO_DIGIT_YEARS
    127 # define DEPRECATE_TWO_DIGIT_YEARS 0
    128 #endif
    129 
    130 #ifdef __weak_alias
    131 __weak_alias(strftime_l, _strftime_l)
    132 __weak_alias(strftime_lz, _strftime_lz)
    133 __weak_alias(strftime_z, _strftime_z)
    134 #endif
    135 
    136 #include "sys/localedef.h"
    137 #define _TIME_LOCALE(loc) \
    138     ((_TimeLocale *)((loc)->part_impl[LC_TIME]))
    139 #define c_fmt   d_t_fmt
    140 
    141 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
    142 
    143 static char *	_add(const char *, char *, const char *);
    144 static char *	_conv(int, const char *, char *, const char *, locale_t);
    145 static char *	_fmt(const timezone_t, const char *, const struct tm *, char *,
    146 			const char *, enum warn *, locale_t);
    147 static char *	_yconv(int, int, bool, bool, char *, const char *, locale_t);
    148 
    149 #ifndef YEAR_2000_NAME
    150 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
    151 #endif /* !defined YEAR_2000_NAME */
    152 
    153 #define	IN_NONE	0
    154 #define	IN_SOME	1
    155 #define	IN_THIS	2
    156 #define	IN_ALL	3
    157 
    158 #define	PAD_DEFAULT	0
    159 #define	PAD_LESS	1
    160 #define	PAD_SPACE	2
    161 #define	PAD_ZERO	3
    162 
    163 static const char fmt_padding[][4][5] = {
    164 	/* DEFAULT,	LESS,	SPACE,	ZERO */
    165 #define	PAD_FMT_MONTHDAY	0
    166 #define	PAD_FMT_HMS		0
    167 #define	PAD_FMT_CENTURY		0
    168 #define	PAD_FMT_SHORTYEAR	0
    169 #define	PAD_FMT_MONTH		0
    170 #define	PAD_FMT_WEEKOFYEAR	0
    171 #define	PAD_FMT_DAYOFMONTH	0
    172 	{ "%02d",	"%d",	"%2d",	"%02d" },
    173 #define	PAD_FMT_SDAYOFMONTH	1
    174 #define	PAD_FMT_SHMS		1
    175 	{ "%2d",	"%d",	"%2d",	"%02d" },
    176 #define	PAD_FMT_DAYOFYEAR	2
    177 	{ "%03d",	"%d",	"%3d",	"%03d" },
    178 #define	PAD_FMT_YEAR		3
    179 	{ "%04d",	"%d",	"%4d",	"%04d" }
    180 };
    181 
    182 size_t
    183 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
    184     const char * __restrict format, const struct tm * __restrict t)
    185 {
    186 	return strftime_lz(sp, s, maxsize, format, t, _current_locale());
    187 }
    188 
    189 #if HAVE_STRFTIME_L
    190 size_t
    191 strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
    192 	   struct tm const *restrict t,
    193 	   ATTRIBUTE_MAYBE_UNUSED locale_t locale)
    194 {
    195   /* Just call strftime, as only the C locale is supported.  */
    196   return strftime(s, maxsize, format, t);
    197 }
    198 #endif
    199 
    200 size_t
    201 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
    202     const char *const format, const struct tm *const t, locale_t loc)
    203 {
    204 	char *	p;
    205 	int saved_errno = errno;
    206 	enum warn warn = IN_NONE;
    207 
    208 	p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
    209 	if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS
    210 	    && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
    211 		(void) fprintf(stderr, "\n");
    212 		(void) fprintf(stderr, "strftime format \"%s\" ", format);
    213 		(void) fprintf(stderr, "yields only two digits of years in ");
    214 		if (warn == IN_SOME)
    215 			(void) fprintf(stderr, "some locales");
    216 		else if (warn == IN_THIS)
    217 			(void) fprintf(stderr, "the current locale");
    218 		else	(void) fprintf(stderr, "all locales");
    219 		(void) fprintf(stderr, "\n");
    220 	}
    221 	if (p == s + maxsize) {
    222 		errno = ERANGE;
    223 		return 0;
    224 	}
    225 	*p = '\0';
    226 	errno = saved_errno;
    227 	return p - s;
    228 }
    229 
    230 static char *
    231 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
    232      const char *ptlim, enum warn *warnp, locale_t loc)
    233 {
    234 	int Ealternative, Oalternative, PadIndex;
    235 	_TimeLocale *tptr = _TIME_LOCALE(loc);
    236 
    237 	for ( ; *format; ++format) {
    238 		if (*format == '%') {
    239 			Ealternative = 0;
    240 			Oalternative = 0;
    241 			PadIndex = PAD_DEFAULT;
    242 label:
    243 			switch (*++format) {
    244 			case '\0':
    245 				/* Output unknown conversion specifiers as-is,
    246 				   to aid debugging.  This includes '%' at
    247 				   format end.  This conforms to C23 section
    248 				   7.29.3.5 paragraph 6, which says behavior
    249 				   is undefined here.  */
    250 			default:
    251 				--format;
    252 				break;
    253 			case 'A':
    254 				pt = _add((t->tm_wday < 0 ||
    255 					t->tm_wday >= DAYSPERWEEK) ?
    256 					"?" : tptr->day[t->tm_wday],
    257 					pt, ptlim);
    258 				continue;
    259 			case 'a':
    260 				pt = _add((t->tm_wday < 0 ||
    261 					t->tm_wday >= DAYSPERWEEK) ?
    262 					"?" : tptr->abday[t->tm_wday],
    263 					pt, ptlim);
    264 				continue;
    265 			case 'B':
    266 				pt = _add((t->tm_mon < 0 ||
    267 					t->tm_mon >= MONSPERYEAR) ?
    268 					"?" :
    269 					/* no alt_month in _TimeLocale */
    270 					(Oalternative ? tptr->mon/*alt_month*/:
    271 					tptr->mon)[t->tm_mon],
    272 					pt, ptlim);
    273 				continue;
    274 			case 'b':
    275 			case 'h':
    276 				pt = _add((t->tm_mon < 0 ||
    277 					t->tm_mon >= MONSPERYEAR) ?
    278 					"?" : tptr->abmon[t->tm_mon],
    279 					pt, ptlim);
    280 				continue;
    281 			case 'C':
    282 				/*
    283 				** %C used to do a...
    284 				**	_fmt("%a %b %e %X %Y", t);
    285 				** ...whereas now POSIX 1003.2 calls for
    286 				** something completely different.
    287 				** (ado, 1993-05-24)
    288 				*/
    289 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    290 					    true, false, pt, ptlim, loc);
    291 				continue;
    292 			case 'c':
    293 				{
    294 				enum warn warn2 = IN_SOME;
    295 
    296 				pt = _fmt(sp, tptr->c_fmt, t, pt,
    297 				    ptlim, &warn2, loc);
    298 				if (warn2 == IN_ALL)
    299 					warn2 = IN_THIS;
    300 				if (warn2 > *warnp)
    301 					*warnp = warn2;
    302 				}
    303 				continue;
    304 			case 'D':
    305 				pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
    306 				    loc);
    307 				continue;
    308 			case 'd':
    309 				pt = _conv(t->tm_mday,
    310 				    fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
    311 				    pt, ptlim, loc);
    312 				continue;
    313 			case 'E':
    314 				if (Ealternative || Oalternative)
    315 					break;
    316 				Ealternative++;
    317 				goto label;
    318 			case 'O':
    319 				/*
    320 				** Locale modifiers of C99 and later.
    321 				** The sequences
    322 				**	%Ec %EC %Ex %EX %Ey %EY
    323 				**	%Od %oe %OH %OI %Om %OM
    324 				**	%OS %Ou %OU %OV %Ow %OW %Oy
    325 				** are supposed to provide alternative
    326 				** representations.
    327 				*/
    328 				if (Ealternative || Oalternative)
    329 					break;
    330 				Oalternative++;
    331 				goto label;
    332 			case 'e':
    333 				pt = _conv(t->tm_mday,
    334 				    fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
    335 				    pt, ptlim, loc);
    336 				continue;
    337 			case 'F':
    338 				pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
    339 				    loc);
    340 				continue;
    341 			case 'H':
    342 				pt = _conv(t->tm_hour,
    343 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    344 				    pt, ptlim, loc);
    345 				continue;
    346 			case 'I':
    347 				pt = _conv((t->tm_hour % 12) ?
    348 				    (t->tm_hour % 12) : 12,
    349 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    350 				    pt, ptlim, loc);
    351 				continue;
    352 			case 'j':
    353 				pt = _conv(t->tm_yday + 1,
    354 				    fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
    355 				    pt, ptlim, loc);
    356 				continue;
    357 			case 'k':
    358 				/*
    359 				** This used to be...
    360 				**	_conv(t->tm_hour % 12 ?
    361 				**		t->tm_hour % 12 : 12, 2, ' ');
    362 				** ...and has been changed to the below to
    363 				** match SunOS 4.1.1 and Arnold Robbins'
    364 				** strftime version 3.0. That is, "%k" and
    365 				** "%l" have been swapped.
    366 				** (ado, 1993-05-24)
    367 				*/
    368 				pt = _conv(t->tm_hour,
    369 				    fmt_padding[PAD_FMT_SHMS][PadIndex],
    370 				    pt, ptlim, loc);
    371 				continue;
    372 #ifdef KITCHEN_SINK
    373 			case 'K':
    374 				/*
    375 				** After all this time, still unclaimed!
    376 				*/
    377 				pt = _add("kitchen sink", pt, ptlim);
    378 				continue;
    379 #endif /* defined KITCHEN_SINK */
    380 			case 'l':
    381 				/*
    382 				** This used to be...
    383 				**	_conv(t->tm_hour, 2, ' ');
    384 				** ...and has been changed to the below to
    385 				** match SunOS 4.1.1 and Arnold Robbin's
    386 				** strftime version 3.0. That is, "%k" and
    387 				** "%l" have been swapped.
    388 				** (ado, 1993-05-24)
    389 				*/
    390 				pt = _conv((t->tm_hour % 12) ?
    391 					(t->tm_hour % 12) : 12,
    392 					fmt_padding[PAD_FMT_SHMS][PadIndex],
    393 					pt, ptlim, loc);
    394 				continue;
    395 			case 'M':
    396 				pt = _conv(t->tm_min,
    397 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    398 				    pt, ptlim, loc);
    399 				continue;
    400 			case 'm':
    401 				pt = _conv(t->tm_mon + 1,
    402 				    fmt_padding[PAD_FMT_MONTH][PadIndex],
    403 				    pt, ptlim, loc);
    404 				continue;
    405 			case 'n':
    406 				pt = _add("\n", pt, ptlim);
    407 				continue;
    408 			case 'p':
    409 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
    410 					tptr->am_pm[1] :
    411 					tptr->am_pm[0],
    412 					pt, ptlim);
    413 				continue;
    414 			case 'R':
    415 				pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
    416 				    loc);
    417 				continue;
    418 			case 'r':
    419 				pt = _fmt(sp, tptr->t_fmt_ampm, t,
    420 				    pt, ptlim, warnp, loc);
    421 				continue;
    422 			case 'S':
    423 				pt = _conv(t->tm_sec,
    424 				    fmt_padding[PAD_FMT_HMS][PadIndex],
    425 				    pt, ptlim, loc);
    426 				continue;
    427 			case 's':
    428 				{
    429 					struct tm	tm;
    430 					char		buf[INT_STRLEN_MAXIMUM(
    431 								time_t) + 1];
    432 					time_t		mkt;
    433 
    434 					tm.tm_sec = t->tm_sec;
    435 					tm.tm_min = t->tm_min;
    436 					tm.tm_hour = t->tm_hour;
    437 					tm.tm_mday = t->tm_mday;
    438 					tm.tm_mon = t->tm_mon;
    439 					tm.tm_year = t->tm_year;
    440 
    441 					/* Get the time_t value for TM.
    442 					   Native time_t, or its redefinition
    443 					   by localtime.c above, is wide enough
    444 					   so that this cannot overflow.  */
    445 #if defined TM_GMTOFF
    446 					mkt = timeoff(&tm, t->TM_GMTOFF);
    447 #else
    448 					tm.tm_isdst = t->tm_isdst;
    449 					mkt = mktime_z(sp, &tm);
    450 #endif
    451 					/* CONSTCOND */
    452 					if (TYPE_SIGNED(time_t)) {
    453 						intmax_t n = mkt;
    454 						(void)snprintf(buf, sizeof(buf),
    455 						    "%"PRIdMAX, n);
    456 					} else {
    457 						/*LINTED possibly unreached*/
    458 						uintmax_t n = mkt;
    459 						(void)snprintf(buf, sizeof(buf),
    460 						    "%"PRIuMAX, n);
    461 					}
    462 					pt = _add(buf, pt, ptlim);
    463 				}
    464 				continue;
    465 			case 'T':
    466 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
    467 				    loc);
    468 				continue;
    469 			case 't':
    470 				pt = _add("\t", pt, ptlim);
    471 				continue;
    472 			case 'U':
    473 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    474 				    t->tm_wday) / DAYSPERWEEK,
    475 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
    476 				    pt, ptlim, loc);
    477 				continue;
    478 			case 'u':
    479 				/*
    480 				** From Arnold Robbins' strftime version 3.0:
    481 				** "ISO 8601: Weekday as a decimal number
    482 				** [1 (Monday) - 7]"
    483 				** (ado, 1993-05-24)
    484 				*/
    485 				pt = _conv((t->tm_wday == 0) ?
    486 					DAYSPERWEEK : t->tm_wday,
    487 					"%d", pt, ptlim, loc);
    488 				continue;
    489 			case 'V':	/* ISO 8601 week number */
    490 			case 'G':	/* ISO 8601 year (four digits) */
    491 			case 'g':	/* ISO 8601 year (two digits) */
    492 /*
    493 ** From Arnold Robbins' strftime version 3.0: "the week number of the
    494 ** year (the first Monday as the first day of week 1) as a decimal number
    495 ** (01-53)."
    496 ** (ado, 1993-05-24)
    497 **
    498 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
    499 ** "Week 01 of a year is per definition the first week which has the
    500 ** Thursday in this year, which is equivalent to the week which contains
    501 ** the fourth day of January. In other words, the first week of a new year
    502 ** is the week which has the majority of its days in the new year. Week 01
    503 ** might also contain days from the previous year and the week before week
    504 ** 01 of a year is the last week (52 or 53) of the previous year even if
    505 ** it contains days from the new year. A week starts with Monday (day 1)
    506 ** and ends with Sunday (day 7). For example, the first week of the year
    507 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
    508 ** (ado, 1996-01-02)
    509 */
    510 				{
    511 					int	year;
    512 					int	base;
    513 					int	yday;
    514 					int	wday;
    515 					int	w;
    516 
    517 					year = t->tm_year;
    518 					base = TM_YEAR_BASE;
    519 					yday = t->tm_yday;
    520 					wday = t->tm_wday;
    521 					for ( ; ; ) {
    522 						int	len;
    523 						int	bot;
    524 						int	top;
    525 
    526 						len = isleap_sum(year, base) ?
    527 							DAYSPERLYEAR :
    528 							DAYSPERNYEAR;
    529 						/*
    530 						** What yday (-3 ... 3) does
    531 						** the ISO year begin on?
    532 						*/
    533 						bot = ((yday + 11 - wday) %
    534 							DAYSPERWEEK) - 3;
    535 						/*
    536 						** What yday does the NEXT
    537 						** ISO year begin on?
    538 						*/
    539 						top = bot -
    540 							(len % DAYSPERWEEK);
    541 						if (top < -3)
    542 							top += DAYSPERWEEK;
    543 						top += len;
    544 						if (yday >= top) {
    545 							++base;
    546 							w = 1;
    547 							break;
    548 						}
    549 						if (yday >= bot) {
    550 							w = 1 + ((yday - bot) /
    551 								DAYSPERWEEK);
    552 							break;
    553 						}
    554 						--base;
    555 						yday += isleap_sum(year, base) ?
    556 							DAYSPERLYEAR :
    557 							DAYSPERNYEAR;
    558 					}
    559 #ifdef XPG4_1994_04_09
    560 					if ((w == 52 &&
    561 						t->tm_mon == TM_JANUARY) ||
    562 						(w == 1 &&
    563 						t->tm_mon == TM_DECEMBER))
    564 							w = 53;
    565 #endif /* defined XPG4_1994_04_09 */
    566 					if (*format == 'V')
    567 						pt = _conv(w,
    568 						    fmt_padding[
    569 						    PAD_FMT_WEEKOFYEAR][
    570 						    PadIndex], pt, ptlim, loc);
    571 					else if (*format == 'g') {
    572 						*warnp = IN_ALL;
    573 						pt = _yconv(year, base,
    574 							false, true,
    575 							pt, ptlim, loc);
    576 					} else	pt = _yconv(year, base,
    577 							true, true,
    578 							pt, ptlim, loc);
    579 				}
    580 				continue;
    581 			case 'v':
    582 				/*
    583 				** From Arnold Robbins' strftime version 3.0:
    584 				** "date as dd-bbb-YYYY"
    585 				** (ado, 1993-05-24)
    586 				*/
    587 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
    588 				    loc);
    589 				continue;
    590 			case 'W':
    591 				pt = _conv((t->tm_yday + DAYSPERWEEK -
    592 				    (t->tm_wday ?
    593 				    (t->tm_wday - 1) :
    594 				    (DAYSPERWEEK - 1))) / DAYSPERWEEK,
    595 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
    596 				    pt, ptlim, loc);
    597 				continue;
    598 			case 'w':
    599 				pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
    600 				continue;
    601 			case 'X':
    602 				pt = _fmt(sp, tptr->t_fmt, t, pt,
    603 				    ptlim, warnp, loc);
    604 				continue;
    605 			case 'x':
    606 				{
    607 				enum warn warn2 = IN_SOME;
    608 
    609 				pt = _fmt(sp, tptr->d_fmt, t, pt,
    610 				    ptlim, &warn2, loc);
    611 				if (warn2 == IN_ALL)
    612 					warn2 = IN_THIS;
    613 				if (warn2 > *warnp)
    614 					*warnp = warn2;
    615 				}
    616 				continue;
    617 			case 'y':
    618 				*warnp = IN_ALL;
    619 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    620 					false, true,
    621 					pt, ptlim, loc);
    622 				continue;
    623 			case 'Y':
    624 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
    625 					true, true,
    626 					pt, ptlim, loc);
    627 				continue;
    628 			case 'Z':
    629 #ifdef TM_ZONE
    630 				pt = _add(t->TM_ZONE, pt, ptlim);
    631 #elif HAVE_TZNAME
    632 				if (t->tm_isdst >= 0) {
    633 					int oerrno = errno, dst = t->tm_isdst;
    634 					const char *z =
    635 					    tzgetname(sp, dst);
    636 					if (z == NULL)
    637 						z = tzgetname(sp, !dst);
    638 					if (z != NULL)
    639 						pt = _add(z, pt, ptlim);
    640 					errno = oerrno;
    641 				}
    642 #endif
    643 				/*
    644 				** C99 and later say that %Z must be
    645 				** replaced by the empty string if the
    646 				** time zone abbreviation is not
    647 				** determinable.
    648 				*/
    649 				continue;
    650 			case 'z':
    651 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
    652 				{
    653 				long		diff;
    654 				char const *	sign;
    655 				bool negative;
    656 
    657 				if (t->tm_isdst < 0)
    658 					continue;
    659 # ifdef TM_GMTOFF
    660 				diff = (int)t->TM_GMTOFF;
    661 # else
    662 				/*
    663 				** C99 and later say that the UT offset must
    664 				** be computed by looking only at
    665 				** tm_isdst. This requirement is
    666 				** incorrect, since it means the code
    667 				** must rely on magic (in this case
    668 				** altzone and timezone), and the
    669 				** magic might not have the correct
    670 				** offset. Doing things correctly is
    671 				** tricky and requires disobeying the standard;
    672 				** see GNU C strftime for details.
    673 				** For now, punt and conform to the
    674 				** standard, even though it's incorrect.
    675 				**
    676 				** C99 and later say that %z must be replaced by
    677 				** the empty string if the time zone is not
    678 				** determinable, so output nothing if the
    679 				** appropriate variables are not available.
    680 				*/
    681 #  ifndef STD_INSPIRED
    682 				if (t->tm_isdst == 0)
    683 #   if USG_COMPAT
    684 					diff = -timezone;
    685 #   else
    686 					continue;
    687 #   endif
    688 				else
    689 #   if ALTZONE
    690 					diff = -altzone;
    691 #   else
    692 					continue;
    693 #   endif
    694 #  else
    695 				{
    696 					struct tm tmp;
    697 					time_t lct, gct;
    698 
    699 					/*
    700 					** Get calendar time from t
    701 					** being treated as local.
    702 					*/
    703 					tmp = *t; /* mktime discards const */
    704 					lct = mktime_z(sp, &tmp);
    705 
    706 					if (lct == (time_t)-1)
    707 						continue;
    708 
    709 					/*
    710 					** Get calendar time from t
    711 					** being treated as GMT.
    712 					**/
    713 					tmp = *t; /* mktime discards const */
    714 					gct = timegm(&tmp);
    715 
    716 					if (gct == (time_t)-1)
    717 						continue;
    718 
    719 					/* LINTED difference will fit int */
    720 					diff = (intmax_t)gct - (intmax_t)lct;
    721 				}
    722 #  endif
    723 # endif
    724 				negative = diff < 0;
    725 				if (diff == 0) {
    726 # ifdef TM_ZONE
    727 				  negative = t->TM_ZONE[0] == '-';
    728 # else
    729 				  negative = t->tm_isdst < 0;
    730 #  if HAVE_TZNAME
    731 				  if (tzname[t->tm_isdst != 0][0] == '-')
    732 				    negative = true;
    733 #  endif
    734 # endif
    735 				}
    736 				if (negative) {
    737 					sign = "-";
    738 					diff = -diff;
    739 				} else	sign = "+";
    740 				pt = _add(sign, pt, ptlim);
    741 				diff /= SECSPERMIN;
    742 				diff = (diff / MINSPERHOUR) * 100 +
    743 					(diff % MINSPERHOUR);
    744 				pt = _conv((int)diff,
    745 				    fmt_padding[PAD_FMT_YEAR][PadIndex],
    746 				    pt, ptlim, loc);
    747 				}
    748 #endif
    749 				continue;
    750 			case '+':
    751 #ifdef notyet
    752 				/* XXX: no date_fmt in _TimeLocale */
    753 				pt = _fmt(sp, tptr->date_fmt, t,
    754 				    pt, ptlim, warnp, loc);
    755 #else
    756 				pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t,
    757 				    pt, ptlim, warnp, loc);
    758 #endif
    759 				continue;
    760 			case '-':
    761 				if (PadIndex != PAD_DEFAULT)
    762 					break;
    763 				PadIndex = PAD_LESS;
    764 				goto label;
    765 			case '_':
    766 				if (PadIndex != PAD_DEFAULT)
    767 					break;
    768 				PadIndex = PAD_SPACE;
    769 				goto label;
    770 			case '0':
    771 				if (PadIndex != PAD_DEFAULT)
    772 					break;
    773 				PadIndex = PAD_ZERO;
    774 				goto label;
    775 			case '%':
    776 				break;
    777 			}
    778 		}
    779 		if (pt == ptlim)
    780 			break;
    781 		*pt++ = *format;
    782 	}
    783 	return pt;
    784 }
    785 
    786 size_t
    787 strftime(char *restrict s, size_t maxsize, char const *restrict format,
    788 	 struct tm const *restrict t)
    789 {
    790 	size_t r;
    791 	time_t now = __lcl_get_monotonic_time();
    792 	int err = __lcl_lock();
    793 	if (0 < err) {
    794 		errno = err;
    795 		return 0;
    796 	}
    797 	tzset_unlocked(!err, false, now);
    798 
    799 	r = strftime_z(__lcl_ptr, s, maxsize, format, t);
    800 
    801 	__lcl_unlock(!err);
    802 
    803 	return r;
    804 }
    805 
    806 size_t
    807 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
    808     const struct tm * __restrict t, locale_t loc)
    809 {
    810 	size_t r;
    811 	time_t now = __lcl_get_monotonic_time();
    812 	int err = __lcl_lock();
    813 	if (0 < err) {
    814 		errno = err;
    815 		return 0;
    816 	}
    817 	tzset_unlocked(!err, false, now);
    818 
    819 	r = strftime_lz(__lcl_ptr, s, maxsize, format, t, loc);
    820 
    821 	__lcl_unlock(!err);
    822 
    823 	return r;
    824 }
    825 
    826 static char *
    827 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc)
    828 {
    829 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
    830 
    831 	(void) snprintf_l(buf, sizeof(buf), loc, format, n);
    832 	return _add(buf, pt, ptlim);
    833 }
    834 
    835 static char *
    836 _add(const char *str, char *pt, const char *ptlim)
    837 {
    838 	while (pt < ptlim && (*pt = *str++) != '\0')
    839 		++pt;
    840 	return pt;
    841 }
    842 
    843 /*
    844 ** POSIX and the C Standard are unclear or inconsistent about
    845 ** what %C and %y do if the year is negative or exceeds 9999.
    846 ** Use the convention that %C concatenated with %y yields the
    847 ** same output as %Y, and that %Y contains at least 4 bytes,
    848 ** with more only if necessary.
    849 */
    850 
    851 static char *
    852 _yconv(int a, int b, bool convert_top, bool convert_yy,
    853     char *pt, const char * ptlim, locale_t loc)
    854 {
    855 	int	lead;
    856 	int	trail;
    857 
    858 	int DIVISOR = 100;
    859 	trail = a % DIVISOR + b % DIVISOR;
    860 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
    861 	trail %= DIVISOR;
    862 	if (trail < 0 && lead > 0) {
    863 		trail += DIVISOR;
    864 		--lead;
    865 	} else if (lead < 0 && trail > 0) {
    866 		trail -= DIVISOR;
    867 		++lead;
    868 	}
    869 	if (convert_top) {
    870 		if (lead == 0 && trail < 0)
    871 			pt = _add("-0", pt, ptlim);
    872 		else	pt = _conv(lead, "%02d", pt, ptlim, loc);
    873 	}
    874 	if (convert_yy)
    875 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim,
    876 		    loc);
    877 	return pt;
    878 }
    879