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