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