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