xref: /netbsd-src/lib/libc/time/strftime.c (revision 3f351f34c6d827cf017cdcff3543f6ec0c88b420)
1 /*	$NetBSD: strftime.c,v 1.52 2023/09/16 18:40:26 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.52 2023/09/16 18:40:26 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 					tm.tm_isdst = t->tm_isdst;
385 #if defined TM_GMTOFF && ! UNINIT_TRAP
386 					tm.TM_GMTOFF = t->TM_GMTOFF;
387 #endif
388 					mkt = mktime_z(sp, &tm);
389 					/* If mktime fails, %s expands to the
390 					   value of (time_t) -1 as a failure
391 					   marker; this is better in practice
392 					   than strftime failing.  */
393 					/* CONSTCOND */
394 					if (TYPE_SIGNED(time_t)) {
395 						intmax_t n = mkt;
396 						(void)snprintf(buf, sizeof(buf),
397 						    "%"PRIdMAX, n);
398 					} else {
399 						uintmax_t n = mkt;
400 						(void)snprintf(buf, sizeof(buf),
401 						    "%"PRIuMAX, n);
402 					}
403 					pt = _add(buf, pt, ptlim);
404 				}
405 				continue;
406 			case 'T':
407 				pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
408 				    loc);
409 				continue;
410 			case 't':
411 				pt = _add("\t", pt, ptlim);
412 				continue;
413 			case 'U':
414 				pt = _conv((t->tm_yday + DAYSPERWEEK -
415 				    t->tm_wday) / DAYSPERWEEK,
416 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
417 				    pt, ptlim, loc);
418 				continue;
419 			case 'u':
420 				/*
421 				** From Arnold Robbins' strftime version 3.0:
422 				** "ISO 8601: Weekday as a decimal number
423 				** [1 (Monday) - 7]"
424 				** (ado, 1993-05-24)
425 				*/
426 				pt = _conv((t->tm_wday == 0) ?
427 					DAYSPERWEEK : t->tm_wday,
428 					"%d", pt, ptlim, loc);
429 				continue;
430 			case 'V':	/* ISO 8601 week number */
431 			case 'G':	/* ISO 8601 year (four digits) */
432 			case 'g':	/* ISO 8601 year (two digits) */
433 /*
434 ** From Arnold Robbins' strftime version 3.0: "the week number of the
435 ** year (the first Monday as the first day of week 1) as a decimal number
436 ** (01-53)."
437 ** (ado, 1993-05-24)
438 **
439 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
440 ** "Week 01 of a year is per definition the first week which has the
441 ** Thursday in this year, which is equivalent to the week which contains
442 ** the fourth day of January. In other words, the first week of a new year
443 ** is the week which has the majority of its days in the new year. Week 01
444 ** might also contain days from the previous year and the week before week
445 ** 01 of a year is the last week (52 or 53) of the previous year even if
446 ** it contains days from the new year. A week starts with Monday (day 1)
447 ** and ends with Sunday (day 7). For example, the first week of the year
448 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
449 ** (ado, 1996-01-02)
450 */
451 				{
452 					int	year;
453 					int	base;
454 					int	yday;
455 					int	wday;
456 					int	w;
457 
458 					year = t->tm_year;
459 					base = TM_YEAR_BASE;
460 					yday = t->tm_yday;
461 					wday = t->tm_wday;
462 					for ( ; ; ) {
463 						int	len;
464 						int	bot;
465 						int	top;
466 
467 						len = isleap_sum(year, base) ?
468 							DAYSPERLYEAR :
469 							DAYSPERNYEAR;
470 						/*
471 						** What yday (-3 ... 3) does
472 						** the ISO year begin on?
473 						*/
474 						bot = ((yday + 11 - wday) %
475 							DAYSPERWEEK) - 3;
476 						/*
477 						** What yday does the NEXT
478 						** ISO year begin on?
479 						*/
480 						top = bot -
481 							(len % DAYSPERWEEK);
482 						if (top < -3)
483 							top += DAYSPERWEEK;
484 						top += len;
485 						if (yday >= top) {
486 							++base;
487 							w = 1;
488 							break;
489 						}
490 						if (yday >= bot) {
491 							w = 1 + ((yday - bot) /
492 								DAYSPERWEEK);
493 							break;
494 						}
495 						--base;
496 						yday += isleap_sum(year, base) ?
497 							DAYSPERLYEAR :
498 							DAYSPERNYEAR;
499 					}
500 #ifdef XPG4_1994_04_09
501 					if ((w == 52 &&
502 						t->tm_mon == TM_JANUARY) ||
503 						(w == 1 &&
504 						t->tm_mon == TM_DECEMBER))
505 							w = 53;
506 #endif /* defined XPG4_1994_04_09 */
507 					if (*format == 'V')
508 						pt = _conv(w,
509 						    fmt_padding[
510 						    PAD_FMT_WEEKOFYEAR][
511 						    PadIndex], pt, ptlim, loc);
512 					else if (*format == 'g') {
513 						*warnp = IN_ALL;
514 						pt = _yconv(year, base,
515 							false, true,
516 							pt, ptlim, loc);
517 					} else	pt = _yconv(year, base,
518 							true, true,
519 							pt, ptlim, loc);
520 				}
521 				continue;
522 			case 'v':
523 				/*
524 				** From Arnold Robbins' strftime version 3.0:
525 				** "date as dd-bbb-YYYY"
526 				** (ado, 1993-05-24)
527 				*/
528 				pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
529 				    loc);
530 				continue;
531 			case 'W':
532 				pt = _conv((t->tm_yday + DAYSPERWEEK -
533 				    (t->tm_wday ?
534 				    (t->tm_wday - 1) :
535 				    (DAYSPERWEEK - 1))) / DAYSPERWEEK,
536 				    fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
537 				    pt, ptlim, loc);
538 				continue;
539 			case 'w':
540 				pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
541 				continue;
542 			case 'X':
543 				pt = _fmt(sp, tptr->t_fmt, t, pt,
544 				    ptlim, warnp, loc);
545 				continue;
546 			case 'x':
547 				{
548 				enum warn warn2 = IN_SOME;
549 
550 				pt = _fmt(sp, tptr->d_fmt, t, pt,
551 				    ptlim, &warn2, loc);
552 				if (warn2 == IN_ALL)
553 					warn2 = IN_THIS;
554 				if (warn2 > *warnp)
555 					*warnp = warn2;
556 				}
557 				continue;
558 			case 'y':
559 				*warnp = IN_ALL;
560 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
561 					false, true,
562 					pt, ptlim, loc);
563 				continue;
564 			case 'Y':
565 				pt = _yconv(t->tm_year, TM_YEAR_BASE,
566 					true, true,
567 					pt, ptlim, loc);
568 				continue;
569 			case 'Z':
570 #ifdef TM_ZONE
571 				pt = _add(t->TM_ZONE, pt, ptlim);
572 #elif HAVE_TZNAME
573 				if (t->tm_isdst >= 0) {
574 					int oerrno = errno, dst = t->tm_isdst;
575 					const char *z =
576 					    tzgetname(sp, dst);
577 					if (z == NULL)
578 						z = tzgetname(sp, !dst);
579 					if (z != NULL)
580 						pt = _add(z, pt, ptlim);
581 					errno = oerrno;
582 				}
583 #endif
584 				/*
585 				** C99 and later say that %Z must be
586 				** replaced by the empty string if the
587 				** time zone abbreviation is not
588 				** determinable.
589 				*/
590 				continue;
591 			case 'z':
592 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
593 				{
594 				long		diff;
595 				char const *	sign;
596 				bool negative;
597 
598 				if (t->tm_isdst < 0)
599 					continue;
600 # ifdef TM_GMTOFF
601 				diff = (int)t->TM_GMTOFF;
602 # else
603 				/*
604 				** C99 and later say that the UT offset must
605 				** be computed by looking only at
606 				** tm_isdst. This requirement is
607 				** incorrect, since it means the code
608 				** must rely on magic (in this case
609 				** altzone and timezone), and the
610 				** magic might not have the correct
611 				** offset. Doing things correctly is
612 				** tricky and requires disobeying the standard;
613 				** see GNU C strftime for details.
614 				** For now, punt and conform to the
615 				** standard, even though it's incorrect.
616 				**
617 				** C99 and later say that %z must be replaced by
618 				** the empty string if the time zone is not
619 				** determinable, so output nothing if the
620 				** appropriate variables are not available.
621 				*/
622 #  ifndef STD_INSPIRED
623 				if (t->tm_isdst == 0)
624 #   if USG_COMPAT
625 					diff = -timezone;
626 #   else
627 					continue;
628 #   endif
629 				else
630 #   if ALTZONE
631 					diff = -altzone;
632 #   else
633 					continue;
634 #   endif
635 #  else
636 				{
637 					struct tm tmp;
638 					time_t lct, gct;
639 
640 					/*
641 					** Get calendar time from t
642 					** being treated as local.
643 					*/
644 					tmp = *t; /* mktime discards const */
645 					lct = mktime_z(sp, &tmp);
646 
647 					if (lct == (time_t)-1)
648 						continue;
649 
650 					/*
651 					** Get calendar time from t
652 					** being treated as GMT.
653 					**/
654 					tmp = *t; /* mktime discards const */
655 					gct = timegm(&tmp);
656 
657 					if (gct == (time_t)-1)
658 						continue;
659 
660 					/* LINTED difference will fit int */
661 					diff = (intmax_t)gct - (intmax_t)lct;
662 				}
663 #  endif
664 # endif
665 				negative = diff < 0;
666 				if (diff == 0) {
667 # ifdef TM_ZONE
668 				  negative = t->TM_ZONE[0] == '-';
669 # else
670 				  negative = t->tm_isdst < 0;
671 #  if HAVE_TZNAME
672 				  if (tzname[t->tm_isdst != 0][0] == '-')
673 				    negative = true;
674 #  endif
675 # endif
676 				}
677 				if (negative) {
678 					sign = "-";
679 					diff = -diff;
680 				} else	sign = "+";
681 				pt = _add(sign, pt, ptlim);
682 				diff /= SECSPERMIN;
683 				diff = (diff / MINSPERHOUR) * 100 +
684 					(diff % MINSPERHOUR);
685 				_DIAGASSERT(__type_fit(int, diff));
686 				pt = _conv((int)diff,
687 				    fmt_padding[PAD_FMT_YEAR][PadIndex],
688 				    pt, ptlim, loc);
689 				}
690 #endif
691 				continue;
692 			case '+':
693 #ifdef notyet
694 				/* XXX: no date_fmt in _TimeLocale */
695 				pt = _fmt(sp, tptr->date_fmt, t,
696 				    pt, ptlim, warnp, loc);
697 #else
698 				pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t,
699 				    pt, ptlim, warnp, loc);
700 #endif
701 				continue;
702 			case '-':
703 				if (PadIndex != PAD_DEFAULT)
704 					break;
705 				PadIndex = PAD_LESS;
706 				goto label;
707 			case '_':
708 				if (PadIndex != PAD_DEFAULT)
709 					break;
710 				PadIndex = PAD_SPACE;
711 				goto label;
712 			case '0':
713 				if (PadIndex != PAD_DEFAULT)
714 					break;
715 				PadIndex = PAD_ZERO;
716 				goto label;
717 			case '%':
718 			/*
719 			** X311J/88-090 (4.12.3.5): if conversion char is
720 			** undefined, behavior is undefined. Print out the
721 			** character itself as printf(3) also does.
722 			*/
723 			default:
724 				break;
725 			}
726 		}
727 		if (pt == ptlim)
728 			break;
729 		*pt++ = *format;
730 	}
731 	return pt;
732 }
733 
734 size_t
735 strftime(char *restrict s, size_t maxsize, char const *restrict format,
736 	 struct tm const *restrict t)
737 {
738 	size_t r;
739 
740 	rwlock_wrlock(&__lcl_lock);
741 	tzset_unlocked();
742 	r = strftime_z(__lclptr, s, maxsize, format, t);
743 	rwlock_unlock(&__lcl_lock);
744 
745 	return r;
746 }
747 
748 size_t
749 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
750     const struct tm * __restrict t, locale_t loc)
751 {
752 	size_t r;
753 
754 	rwlock_wrlock(&__lcl_lock);
755 	tzset_unlocked();
756 	r = strftime_lz(__lclptr, s, maxsize, format, t, loc);
757 	rwlock_unlock(&__lcl_lock);
758 
759 	return r;
760 }
761 
762 static char *
763 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc)
764 {
765 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
766 
767 	(void) snprintf_l(buf, sizeof(buf), loc, format, n);
768 	return _add(buf, pt, ptlim);
769 }
770 
771 static char *
772 _add(const char *str, char *pt, const char *ptlim)
773 {
774 	while (pt < ptlim && (*pt = *str++) != '\0')
775 		++pt;
776 	return pt;
777 }
778 
779 /*
780 ** POSIX and the C Standard are unclear or inconsistent about
781 ** what %C and %y do if the year is negative or exceeds 9999.
782 ** Use the convention that %C concatenated with %y yields the
783 ** same output as %Y, and that %Y contains at least 4 bytes,
784 ** with more only if necessary.
785 */
786 
787 static char *
788 _yconv(int a, int b, bool convert_top, bool convert_yy,
789     char *pt, const char * ptlim, locale_t loc)
790 {
791 	int	lead;
792 	int	trail;
793 
794 	int DIVISOR = 100;
795 	trail = a % DIVISOR + b % DIVISOR;
796 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
797 	trail %= DIVISOR;
798 	if (trail < 0 && lead > 0) {
799 		trail += DIVISOR;
800 		--lead;
801 	} else if (lead < 0 && trail > 0) {
802 		trail -= DIVISOR;
803 		++lead;
804 	}
805 	if (convert_top) {
806 		if (lead == 0 && trail < 0)
807 			pt = _add("-0", pt, ptlim);
808 		else	pt = _conv(lead, "%02d", pt, ptlim, loc);
809 	}
810 	if (convert_yy)
811 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim,
812 		    loc);
813 	return pt;
814 }
815