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