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