strftime.c revision 1.42 1 /* $NetBSD: strftime.c,v 1.42 2018/10/19 23:05:35 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.42 2018/10/19 23:05:35 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 *);
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 *);
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 size_t
100 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize,
101 const char * __restrict format, const struct tm * __restrict t)
102 {
103 return strftime_lz(sp, s, maxsize, format, t, _current_locale());
104 }
105
106 #if HAVE_STRFTIME_L
107 size_t
108 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
109 locale_t locale)
110 {
111 /* Just call strftime, as only the C locale is supported. */
112 return strftime(s, maxsize, format, t);
113 }
114 #endif
115
116 size_t
117 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize,
118 const char *const format, const struct tm *const t, locale_t loc)
119 {
120 char * p;
121 enum warn warn = IN_NONE;
122
123 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc);
124 if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS
125 && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
126 (void) fprintf(stderr, "\n");
127 (void) fprintf(stderr, "strftime format \"%s\" ", format);
128 (void) fprintf(stderr, "yields only two digits of years in ");
129 if (warn == IN_SOME)
130 (void) fprintf(stderr, "some locales");
131 else if (warn == IN_THIS)
132 (void) fprintf(stderr, "the current locale");
133 else (void) fprintf(stderr, "all locales");
134 (void) fprintf(stderr, "\n");
135 }
136 if (p == s + maxsize)
137 return 0;
138 *p = '\0';
139 return p - s;
140 }
141
142 static char *
143 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt,
144 const char *ptlim, enum warn *warnp, locale_t loc)
145 {
146 for ( ; *format; ++format) {
147 if (*format == '%') {
148 label:
149 switch (*++format) {
150 case '\0':
151 --format;
152 break;
153 case 'A':
154 pt = _add((t->tm_wday < 0 ||
155 t->tm_wday >= DAYSPERWEEK) ?
156 "?" : _TIME_LOCALE(loc)->day[t->tm_wday],
157 pt, ptlim);
158 continue;
159 case 'a':
160 pt = _add((t->tm_wday < 0 ||
161 t->tm_wday >= DAYSPERWEEK) ?
162 "?" : _TIME_LOCALE(loc)->abday[t->tm_wday],
163 pt, ptlim);
164 continue;
165 case 'B':
166 pt = _add((t->tm_mon < 0 ||
167 t->tm_mon >= MONSPERYEAR) ?
168 "?" : _TIME_LOCALE(loc)->mon[t->tm_mon],
169 pt, ptlim);
170 continue;
171 case 'b':
172 case 'h':
173 pt = _add((t->tm_mon < 0 ||
174 t->tm_mon >= MONSPERYEAR) ?
175 "?" : _TIME_LOCALE(loc)->abmon[t->tm_mon],
176 pt, ptlim);
177 continue;
178 case 'C':
179 /*
180 ** %C used to do a...
181 ** _fmt("%a %b %e %X %Y", t);
182 ** ...whereas now POSIX 1003.2 calls for
183 ** something completely different.
184 ** (ado, 1993-05-24)
185 */
186 pt = _yconv(t->tm_year, TM_YEAR_BASE,
187 true, false, pt, ptlim);
188 continue;
189 case 'c':
190 {
191 enum warn warn2 = IN_SOME;
192
193 pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt,
194 ptlim, &warn2, loc);
195 if (warn2 == IN_ALL)
196 warn2 = IN_THIS;
197 if (warn2 > *warnp)
198 *warnp = warn2;
199 }
200 continue;
201 case 'D':
202 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp,
203 loc);
204 continue;
205 case 'd':
206 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
207 continue;
208 case 'E':
209 case 'O':
210 /*
211 ** Locale modifiers of C99 and later.
212 ** The sequences
213 ** %Ec %EC %Ex %EX %Ey %EY
214 ** %Od %oe %OH %OI %Om %OM
215 ** %OS %Ou %OU %OV %Ow %OW %Oy
216 ** are supposed to provide alternative
217 ** representations.
218 */
219 goto label;
220 case 'e':
221 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
222 continue;
223 case 'F':
224 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp,
225 loc);
226 continue;
227 case 'H':
228 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
229 continue;
230 case 'I':
231 pt = _conv((t->tm_hour % 12) ?
232 (t->tm_hour % 12) : 12,
233 "%02d", pt, ptlim);
234 continue;
235 case 'j':
236 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
237 continue;
238 case 'k':
239 /*
240 ** This used to be...
241 ** _conv(t->tm_hour % 12 ?
242 ** t->tm_hour % 12 : 12, 2, ' ');
243 ** ...and has been changed to the below to
244 ** match SunOS 4.1.1 and Arnold Robbins'
245 ** strftime version 3.0. That is, "%k" and
246 ** "%l" have been swapped.
247 ** (ado, 1993-05-24)
248 */
249 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
250 continue;
251 #ifdef KITCHEN_SINK
252 case 'K':
253 /*
254 ** After all this time, still unclaimed!
255 */
256 pt = _add("kitchen sink", pt, ptlim);
257 continue;
258 #endif /* defined KITCHEN_SINK */
259 case 'l':
260 /*
261 ** This used to be...
262 ** _conv(t->tm_hour, 2, ' ');
263 ** ...and has been changed to the below to
264 ** match SunOS 4.1.1 and Arnold Robbin's
265 ** strftime version 3.0. That is, "%k" and
266 ** "%l" have been swapped.
267 ** (ado, 1993-05-24)
268 */
269 pt = _conv((t->tm_hour % 12) ?
270 (t->tm_hour % 12) : 12,
271 "%2d", pt, ptlim);
272 continue;
273 case 'M':
274 pt = _conv(t->tm_min, "%02d", pt, ptlim);
275 continue;
276 case 'm':
277 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
278 continue;
279 case 'n':
280 pt = _add("\n", pt, ptlim);
281 continue;
282 case 'p':
283 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
284 _TIME_LOCALE(loc)->am_pm[1] :
285 _TIME_LOCALE(loc)->am_pm[0],
286 pt, ptlim);
287 continue;
288 case 'R':
289 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp,
290 loc);
291 continue;
292 case 'r':
293 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t,
294 pt, ptlim, warnp, loc);
295 continue;
296 case 'S':
297 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
298 continue;
299 case 's':
300 {
301 struct tm tm;
302 char buf[INT_STRLEN_MAXIMUM(
303 time_t) + 1];
304 time_t mkt;
305
306 tm = *t;
307 mkt = mktime(&tm);
308 /* CONSTCOND */
309 if (TYPE_SIGNED(time_t))
310 (void)snprintf(buf, sizeof(buf),
311 "%jd", (intmax_t) mkt);
312 else (void)snprintf(buf, sizeof(buf),
313 "%ju", (uintmax_t) mkt);
314 pt = _add(buf, pt, ptlim);
315 }
316 continue;
317 case 'T':
318 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp,
319 loc);
320 continue;
321 case 't':
322 pt = _add("\t", pt, ptlim);
323 continue;
324 case 'U':
325 pt = _conv((t->tm_yday + DAYSPERWEEK -
326 t->tm_wday) / DAYSPERWEEK,
327 "%02d", pt, ptlim);
328 continue;
329 case 'u':
330 /*
331 ** From Arnold Robbins' strftime version 3.0:
332 ** "ISO 8601: Weekday as a decimal number
333 ** [1 (Monday) - 7]"
334 ** (ado, 1993-05-24)
335 */
336 pt = _conv((t->tm_wday == 0) ?
337 DAYSPERWEEK : t->tm_wday,
338 "%d", pt, ptlim);
339 continue;
340 case 'V': /* ISO 8601 week number */
341 case 'G': /* ISO 8601 year (four digits) */
342 case 'g': /* ISO 8601 year (two digits) */
343 /*
344 ** From Arnold Robbins' strftime version 3.0: "the week number of the
345 ** year (the first Monday as the first day of week 1) as a decimal number
346 ** (01-53)."
347 ** (ado, 1993-05-24)
348 **
349 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
350 ** "Week 01 of a year is per definition the first week which has the
351 ** Thursday in this year, which is equivalent to the week which contains
352 ** the fourth day of January. In other words, the first week of a new year
353 ** is the week which has the majority of its days in the new year. Week 01
354 ** might also contain days from the previous year and the week before week
355 ** 01 of a year is the last week (52 or 53) of the previous year even if
356 ** it contains days from the new year. A week starts with Monday (day 1)
357 ** and ends with Sunday (day 7). For example, the first week of the year
358 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
359 ** (ado, 1996-01-02)
360 */
361 {
362 int year;
363 int base;
364 int yday;
365 int wday;
366 int w;
367
368 year = t->tm_year;
369 base = TM_YEAR_BASE;
370 yday = t->tm_yday;
371 wday = t->tm_wday;
372 for ( ; ; ) {
373 int len;
374 int bot;
375 int top;
376
377 len = isleap_sum(year, base) ?
378 DAYSPERLYEAR :
379 DAYSPERNYEAR;
380 /*
381 ** What yday (-3 ... 3) does
382 ** the ISO year begin on?
383 */
384 bot = ((yday + 11 - wday) %
385 DAYSPERWEEK) - 3;
386 /*
387 ** What yday does the NEXT
388 ** ISO year begin on?
389 */
390 top = bot -
391 (len % DAYSPERWEEK);
392 if (top < -3)
393 top += DAYSPERWEEK;
394 top += len;
395 if (yday >= top) {
396 ++base;
397 w = 1;
398 break;
399 }
400 if (yday >= bot) {
401 w = 1 + ((yday - bot) /
402 DAYSPERWEEK);
403 break;
404 }
405 --base;
406 yday += isleap_sum(year, base) ?
407 DAYSPERLYEAR :
408 DAYSPERNYEAR;
409 }
410 #ifdef XPG4_1994_04_09
411 if ((w == 52 &&
412 t->tm_mon == TM_JANUARY) ||
413 (w == 1 &&
414 t->tm_mon == TM_DECEMBER))
415 w = 53;
416 #endif /* defined XPG4_1994_04_09 */
417 if (*format == 'V')
418 pt = _conv(w, "%02d",
419 pt, ptlim);
420 else if (*format == 'g') {
421 *warnp = IN_ALL;
422 pt = _yconv(year, base,
423 false, true,
424 pt, ptlim);
425 } else pt = _yconv(year, base,
426 true, true,
427 pt, ptlim);
428 }
429 continue;
430 case 'v':
431 /*
432 ** From Arnold Robbins' strftime version 3.0:
433 ** "date as dd-bbb-YYYY"
434 ** (ado, 1993-05-24)
435 */
436 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp,
437 loc);
438 continue;
439 case 'W':
440 pt = _conv((t->tm_yday + DAYSPERWEEK -
441 (t->tm_wday ?
442 (t->tm_wday - 1) :
443 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
444 "%02d", pt, ptlim);
445 continue;
446 case 'w':
447 pt = _conv(t->tm_wday, "%d", pt, ptlim);
448 continue;
449 case 'X':
450 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt,
451 ptlim, warnp, loc);
452 continue;
453 case 'x':
454 {
455 enum warn warn2 = IN_SOME;
456
457 pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt,
458 ptlim, &warn2, loc);
459 if (warn2 == IN_ALL)
460 warn2 = IN_THIS;
461 if (warn2 > *warnp)
462 *warnp = warn2;
463 }
464 continue;
465 case 'y':
466 *warnp = IN_ALL;
467 pt = _yconv(t->tm_year, TM_YEAR_BASE,
468 false, true,
469 pt, ptlim);
470 continue;
471 case 'Y':
472 pt = _yconv(t->tm_year, TM_YEAR_BASE,
473 true, true,
474 pt, ptlim);
475 continue;
476 case 'Z':
477 #ifdef TM_ZONE
478 pt = _add(t->TM_ZONE, pt, ptlim);
479 #elif HAVE_TZNAME
480 if (t->tm_isdst >= 0)
481 pt = _add((sp ?
482 tzgetname(sp, t->tm_isdst) :
483 tzname[t->tm_isdst != 0]),
484 pt, ptlim);
485 #endif
486 /*
487 ** C99 and later say that %Z must be
488 ** replaced by the empty string if the
489 ** time zone abbreviation is not
490 ** determinable.
491 */
492 continue;
493 case 'z':
494 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE
495 {
496 long diff;
497 char const * sign;
498 bool negative;
499
500 if (t->tm_isdst < 0)
501 continue;
502 # ifdef TM_GMTOFF
503 diff = (int)t->TM_GMTOFF;
504 # else
505 /*
506 ** C99 and later say that the UT offset must
507 ** be computed by looking only at
508 ** tm_isdst. This requirement is
509 ** incorrect, since it means the code
510 ** must rely on magic (in this case
511 ** altzone and timezone), and the
512 ** magic might not have the correct
513 ** offset. Doing things correctly is
514 ** tricky and requires disobeying the standard;
515 ** see GNU C strftime for details.
516 ** For now, punt and conform to the
517 ** standard, even though it's incorrect.
518 **
519 ** C99 and later say that %z must be replaced by
520 ** the empty string if the time zone is not
521 ** determinable, so output nothing if the
522 ** appropriate variables are not available.
523 */
524 # ifndef STD_INSPIRED
525 if (t->tm_isdst == 0)
526 # if USG_COMPAT
527 diff = -timezone;
528 # else
529 continue;
530 # endif
531 else
532 # ifdef ALTZONE
533 diff = -altzone;
534 # else
535 continue;
536 # endif
537 # else
538 {
539 struct tm tmp;
540 time_t lct, gct;
541
542 /*
543 ** Get calendar time from t
544 ** being treated as local.
545 */
546 tmp = *t; /* mktime discards const */
547 lct = mktime(&tmp);
548
549 if (lct == (time_t)-1)
550 continue;
551
552 /*
553 ** Get calendar time from t
554 ** being treated as GMT.
555 **/
556 tmp = *t; /* mktime discards const */
557 gct = timegm(&tmp);
558
559 if (gct == (time_t)-1)
560 continue;
561
562 /* LINTED difference will fit int */
563 diff = (intmax_t)gct - (intmax_t)lct;
564 }
565 # endif
566 # endif
567 negative = diff < 0;
568 if (diff == 0) {
569 #ifdef TM_ZONE
570 negative = t->TM_ZONE[0] == '-';
571 #else
572 negative = t->tm_isdst < 0;
573 # if HAVE_TZNAME
574 if (tzname[t->tm_isdst != 0][0] == '-')
575 negative = true;
576 # endif
577 #endif
578 }
579 if (negative) {
580 sign = "-";
581 diff = -diff;
582 } else sign = "+";
583 pt = _add(sign, pt, ptlim);
584 diff /= SECSPERMIN;
585 diff = (diff / MINSPERHOUR) * 100 +
586 (diff % MINSPERHOUR);
587 _DIAGASSERT(__type_fit(int, diff));
588 pt = _conv((int)diff, "%04d", pt, ptlim);
589 }
590 #endif
591 continue;
592 #if 0
593 case '+':
594 pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
595 pt, ptlim, warnp, loc);
596 continue;
597 #endif
598 case '%':
599 /*
600 ** X311J/88-090 (4.12.3.5): if conversion char is
601 ** undefined, behavior is undefined. Print out the
602 ** character itself as printf(3) also does.
603 */
604 default:
605 break;
606 }
607 }
608 if (pt == ptlim)
609 break;
610 *pt++ = *format;
611 }
612 return pt;
613 }
614
615 size_t
616 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
617 {
618 tzset();
619 return strftime_z(NULL, s, maxsize, format, t);
620 }
621
622 size_t
623 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
624 const struct tm * __restrict t, locale_t loc)
625 {
626 tzset();
627 return strftime_lz(NULL, s, maxsize, format, t, loc);
628 }
629
630 static char *
631 _conv(int n, const char *format, char *pt, const char *ptlim)
632 {
633 char buf[INT_STRLEN_MAXIMUM(int) + 1];
634
635 (void) snprintf(buf, sizeof(buf), format, n);
636 return _add(buf, pt, ptlim);
637 }
638
639 static char *
640 _add(const char *str, char *pt, const char *ptlim)
641 {
642 while (pt < ptlim && (*pt = *str++) != '\0')
643 ++pt;
644 return pt;
645 }
646
647 /*
648 ** POSIX and the C Standard are unclear or inconsistent about
649 ** what %C and %y do if the year is negative or exceeds 9999.
650 ** Use the convention that %C concatenated with %y yields the
651 ** same output as %Y, and that %Y contains at least 4 bytes,
652 ** with more only if necessary.
653 */
654
655 static char *
656 _yconv(int a, int b, bool convert_top, bool convert_yy,
657 char *pt, const char * ptlim)
658 {
659 int lead;
660 int trail;
661
662 #define DIVISOR 100
663 trail = a % DIVISOR + b % DIVISOR;
664 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
665 trail %= DIVISOR;
666 if (trail < 0 && lead > 0) {
667 trail += DIVISOR;
668 --lead;
669 } else if (lead < 0 && trail > 0) {
670 trail -= DIVISOR;
671 ++lead;
672 }
673 if (convert_top) {
674 if (lead == 0 && trail < 0)
675 pt = _add("-0", pt, ptlim);
676 else pt = _conv(lead, "%02d", pt, ptlim);
677 }
678 if (convert_yy)
679 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
680 return pt;
681 }
682