strftime.c revision 1.41 1 /* $NetBSD: strftime.c,v 1.41 2018/05/04 15:51:00 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.41 2018/05/04 15:51:00 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 is not determinable.
490 */
491 continue;
492 case 'z':
493 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE
494 {
495 long diff;
496 char const * sign;
497 bool negative;
498
499 if (t->tm_isdst < 0)
500 continue;
501 # ifdef TM_GMTOFF
502 diff = (int)t->TM_GMTOFF;
503 # else
504 /*
505 ** C99 and later say that the UT offset must
506 ** be computed by looking only at
507 ** tm_isdst. This requirement is
508 ** incorrect, since it means the code
509 ** must rely on magic (in this case
510 ** altzone and timezone), and the
511 ** magic might not have the correct
512 ** offset. Doing things correctly is
513 ** tricky and requires disobeying the standard;
514 ** see GNU C strftime for details.
515 ** For now, punt and conform to the
516 ** standard, even though it's incorrect.
517 **
518 ** C99 and later say that %z must be replaced by
519 ** the empty string if the time zone is not
520 ** determinable, so output nothing if the
521 ** appropriate variables are not available.
522 */
523 # ifndef STD_INSPIRED
524 if (t->tm_isdst == 0)
525 # if USG_COMPAT
526 diff = -timezone;
527 # else
528 continue;
529 # endif
530 else
531 # ifdef ALTZONE
532 diff = -altzone;
533 # else
534 continue;
535 # endif
536 # else
537 {
538 struct tm tmp;
539 time_t lct, gct;
540
541 /*
542 ** Get calendar time from t
543 ** being treated as local.
544 */
545 tmp = *t; /* mktime discards const */
546 lct = mktime(&tmp);
547
548 if (lct == (time_t)-1)
549 continue;
550
551 /*
552 ** Get calendar time from t
553 ** being treated as GMT.
554 **/
555 tmp = *t; /* mktime discards const */
556 gct = timegm(&tmp);
557
558 if (gct == (time_t)-1)
559 continue;
560
561 /* LINTED difference will fit int */
562 diff = (intmax_t)gct - (intmax_t)lct;
563 }
564 # endif
565 # endif
566 negative = diff < 0;
567 if (diff == 0) {
568 #ifdef TM_ZONE
569 negative = t->TM_ZONE[0] == '-';
570 #else
571 negative = t->tm_isdst < 0;
572 # if HAVE_TZNAME
573 if (tzname[t->tm_isdst != 0][0] == '-')
574 negative = true;
575 # endif
576 #endif
577 }
578 if (negative) {
579 sign = "-";
580 diff = -diff;
581 } else sign = "+";
582 pt = _add(sign, pt, ptlim);
583 diff /= SECSPERMIN;
584 diff = (diff / MINSPERHOUR) * 100 +
585 (diff % MINSPERHOUR);
586 _DIAGASSERT(__type_fit(int, diff));
587 pt = _conv((int)diff, "%04d", pt, ptlim);
588 }
589 #endif
590 continue;
591 #if 0
592 case '+':
593 pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t,
594 pt, ptlim, warnp, loc);
595 continue;
596 #endif
597 case '%':
598 /*
599 ** X311J/88-090 (4.12.3.5): if conversion char is
600 ** undefined, behavior is undefined. Print out the
601 ** character itself as printf(3) also does.
602 */
603 default:
604 break;
605 }
606 }
607 if (pt == ptlim)
608 break;
609 *pt++ = *format;
610 }
611 return pt;
612 }
613
614 size_t
615 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
616 {
617 tzset();
618 return strftime_z(NULL, s, maxsize, format, t);
619 }
620
621 size_t
622 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
623 const struct tm * __restrict t, locale_t loc)
624 {
625 tzset();
626 return strftime_lz(NULL, s, maxsize, format, t, loc);
627 }
628
629 static char *
630 _conv(int n, const char *format, char *pt, const char *ptlim)
631 {
632 char buf[INT_STRLEN_MAXIMUM(int) + 1];
633
634 (void) snprintf(buf, sizeof(buf), format, n);
635 return _add(buf, pt, ptlim);
636 }
637
638 static char *
639 _add(const char *str, char *pt, const char *ptlim)
640 {
641 while (pt < ptlim && (*pt = *str++) != '\0')
642 ++pt;
643 return pt;
644 }
645
646 /*
647 ** POSIX and the C Standard are unclear or inconsistent about
648 ** what %C and %y do if the year is negative or exceeds 9999.
649 ** Use the convention that %C concatenated with %y yields the
650 ** same output as %Y, and that %Y contains at least 4 bytes,
651 ** with more only if necessary.
652 */
653
654 static char *
655 _yconv(int a, int b, bool convert_top, bool convert_yy,
656 char *pt, const char * ptlim)
657 {
658 int lead;
659 int trail;
660
661 #define DIVISOR 100
662 trail = a % DIVISOR + b % DIVISOR;
663 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
664 trail %= DIVISOR;
665 if (trail < 0 && lead > 0) {
666 trail += DIVISOR;
667 --lead;
668 } else if (lead < 0 && trail > 0) {
669 trail -= DIVISOR;
670 ++lead;
671 }
672 if (convert_top) {
673 if (lead == 0 && trail < 0)
674 pt = _add("-0", pt, ptlim);
675 else pt = _conv(lead, "%02d", pt, ptlim);
676 }
677 if (convert_yy)
678 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
679 return pt;
680 }
681