strftime.c revision 1.47 1 /* $NetBSD: strftime.c,v 1.47 2020/05/25 14:52:48 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.47 2020/05/25 14:52:48 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 || 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 # if 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