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