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