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