Home | History | Annotate | Line # | Download | only in time
strftime.c revision 1.44
      1 /*	$NetBSD: strftime.c,v 1.44 2019/04/05 21:27:44 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.44 2019/04/05 21:27:44 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 					pt = _add(tzgetname(sp, t->tm_isdst),
    549 					    pt, ptlim);
    550 #endif
    551 				/*
    552 				** C99 and later say that %Z must be
    553 				** replaced by the empty string if the
    554 				** time zone abbreviation is not
    555 				** determinable.
    556 				*/
    557 				continue;
    558 			case 'z':
    559 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE
    560 				{
    561 				long		diff;
    562 				char const *	sign;
    563 				bool negative;
    564 
    565 				if (t->tm_isdst < 0)
    566 					continue;
    567 # ifdef TM_GMTOFF
    568 				diff = (int)t->TM_GMTOFF;
    569 # else
    570 				/*
    571 				** C99 and later say that the UT offset must
    572 				** be computed by looking only at
    573 				** tm_isdst. This requirement is
    574 				** incorrect, since it means the code
    575 				** must rely on magic (in this case
    576 				** altzone and timezone), and the
    577 				** magic might not have the correct
    578 				** offset. Doing things correctly is
    579 				** tricky and requires disobeying the standard;
    580 				** see GNU C strftime for details.
    581 				** For now, punt and conform to the
    582 				** standard, even though it's incorrect.
    583 				**
    584 				** C99 and later say that %z must be replaced by
    585 				** the empty string if the time zone is not
    586 				** determinable, so output nothing if the
    587 				** appropriate variables are not available.
    588 				*/
    589 #  ifndef STD_INSPIRED
    590 				if (t->tm_isdst == 0)
    591 #   if USG_COMPAT
    592 					diff = -timezone;
    593 #   else
    594 					continue;
    595 #   endif
    596 				else
    597 #   ifdef ALTZONE
    598 					diff = -altzone;
    599 #   else
    600 					continue;
    601 #   endif
    602 #  else
    603 				{
    604 					struct tm tmp;
    605 					time_t lct, gct;
    606 
    607 					/*
    608 					** Get calendar time from t
    609 					** being treated as local.
    610 					*/
    611 					tmp = *t; /* mktime discards const */
    612 					lct = mktime_z(sp, &tmp);
    613 
    614 					if (lct == (time_t)-1)
    615 						continue;
    616 
    617 					/*
    618 					** Get calendar time from t
    619 					** being treated as GMT.
    620 					**/
    621 					tmp = *t; /* mktime discards const */
    622 					gct = timegm(&tmp);
    623 
    624 					if (gct == (time_t)-1)
    625 						continue;
    626 
    627 					/* LINTED difference will fit int */
    628 					diff = (intmax_t)gct - (intmax_t)lct;
    629 				}
    630 #  endif
    631 # endif
    632 				negative = diff < 0;
    633 				if (diff == 0) {
    634 #ifdef TM_ZONE
    635 				  negative = t->TM_ZONE[0] == '-';
    636 #else
    637 				  negative = t->tm_isdst < 0;
    638 # if HAVE_TZNAME
    639 				  if (tzname[t->tm_isdst != 0][0] == '-')
    640 				    negative = true;
    641 # endif
    642 #endif
    643 				}
    644 				if (negative) {
    645 					sign = "-";
    646 					diff = -diff;
    647 				} else	sign = "+";
    648 				pt = _add(sign, pt, ptlim);
    649 				diff /= SECSPERMIN;
    650 				diff = (diff / MINSPERHOUR) * 100 +
    651 					(diff % MINSPERHOUR);
    652 				_DIAGASSERT(__type_fit(int, diff));
    653 				pt = _conv((int)diff,
    654 				    fmt_padding[PAD_FMT_YEAR][PadIndex],
    655 				    pt, ptlim, loc);
    656 				}
    657 #endif
    658 				continue;
    659 			case '+':
    660 #ifdef notyet
    661 				/* XXX: no date_fmt in _TimeLocale */
    662 				pt = _fmt(sp, tptr->date_fmt, t,
    663 				    pt, ptlim, warnp, loc);
    664 #else
    665 				pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t,
    666 				    pt, ptlim, warnp, loc);
    667 #endif
    668 				continue;
    669 			case '-':
    670 				if (PadIndex != PAD_DEFAULT)
    671 					break;
    672 				PadIndex = PAD_LESS;
    673 				goto label;
    674 			case '_':
    675 				if (PadIndex != PAD_DEFAULT)
    676 					break;
    677 				PadIndex = PAD_SPACE;
    678 				goto label;
    679 			case '0':
    680 				if (PadIndex != PAD_DEFAULT)
    681 					break;
    682 				PadIndex = PAD_ZERO;
    683 				goto label;
    684 			case '%':
    685 			/*
    686 			** X311J/88-090 (4.12.3.5): if conversion char is
    687 			** undefined, behavior is undefined. Print out the
    688 			** character itself as printf(3) also does.
    689 			*/
    690 			default:
    691 				break;
    692 			}
    693 		}
    694 		if (pt == ptlim)
    695 			break;
    696 		*pt++ = *format;
    697 	}
    698 	return pt;
    699 }
    700 
    701 size_t
    702 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
    703 {
    704 	size_t r;
    705 
    706 	rwlock_wrlock(&__lcl_lock);
    707 	tzset_unlocked();
    708 	r = strftime_z(__lclptr, s, maxsize, format, t);
    709 	rwlock_unlock(&__lcl_lock);
    710 
    711 	return r;
    712 }
    713 
    714 size_t
    715 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
    716     const struct tm * __restrict t, locale_t loc)
    717 {
    718 	size_t r;
    719 
    720 	rwlock_wrlock(&__lcl_lock);
    721 	tzset_unlocked();
    722 	r = strftime_lz(__lclptr, s, maxsize, format, t, loc);
    723 	rwlock_unlock(&__lcl_lock);
    724 
    725 	return r;
    726 }
    727 
    728 static char *
    729 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc)
    730 {
    731 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
    732 
    733 	(void) snprintf_l(buf, sizeof(buf), loc, format, n);
    734 	return _add(buf, pt, ptlim);
    735 }
    736 
    737 static char *
    738 _add(const char *str, char *pt, const char *ptlim)
    739 {
    740 	while (pt < ptlim && (*pt = *str++) != '\0')
    741 		++pt;
    742 	return pt;
    743 }
    744 
    745 /*
    746 ** POSIX and the C Standard are unclear or inconsistent about
    747 ** what %C and %y do if the year is negative or exceeds 9999.
    748 ** Use the convention that %C concatenated with %y yields the
    749 ** same output as %Y, and that %Y contains at least 4 bytes,
    750 ** with more only if necessary.
    751 */
    752 
    753 static char *
    754 _yconv(int a, int b, bool convert_top, bool convert_yy,
    755     char *pt, const char * ptlim, locale_t loc)
    756 {
    757 	int	lead;
    758 	int	trail;
    759 
    760 #define DIVISOR	100
    761 	trail = a % DIVISOR + b % DIVISOR;
    762 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
    763 	trail %= DIVISOR;
    764 	if (trail < 0 && lead > 0) {
    765 		trail += DIVISOR;
    766 		--lead;
    767 	} else if (lead < 0 && trail > 0) {
    768 		trail -= DIVISOR;
    769 		++lead;
    770 	}
    771 	if (convert_top) {
    772 		if (lead == 0 && trail < 0)
    773 			pt = _add("-0", pt, ptlim);
    774 		else	pt = _conv(lead, "%02d", pt, ptlim, loc);
    775 	}
    776 	if (convert_yy)
    777 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim,
    778 		    loc);
    779 	return pt;
    780 }
    781