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