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