1 /* Convert a broken-down timestamp to a string. */
2
3 /* Copyright 1989 The Regents of the University of California.
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 1. Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 3. Neither the name of the University nor the names of its contributors
15 may be used to endorse or promote products derived from this software
16 without specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 SUCH DAMAGE. */
29
30 /*
31 ** Based on the UCB version with the copyright notice appearing above.
32 **
33 ** This is ANSIish only when "multibyte character == plain character".
34 */
35
36 #include "private.h"
37
38 #include <fcntl.h>
39 #include <locale.h>
40 #include <stdio.h>
41
42 #ifndef DEPRECATE_TWO_DIGIT_YEARS
43 # define DEPRECATE_TWO_DIGIT_YEARS false
44 #endif
45
46 struct lc_time_T {
47 const char * mon[MONSPERYEAR];
48 const char * month[MONSPERYEAR];
49 const char * wday[DAYSPERWEEK];
50 const char * weekday[DAYSPERWEEK];
51 const char * X_fmt;
52 const char * x_fmt;
53 const char * c_fmt;
54 const char * am;
55 const char * pm;
56 const char * date_fmt;
57 };
58
59 static const struct lc_time_T C_time_locale = {
60 {
61 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
62 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
63 }, {
64 "January", "February", "March", "April", "May", "June",
65 "July", "August", "September", "October", "November", "December"
66 }, {
67 "Sun", "Mon", "Tue", "Wed",
68 "Thu", "Fri", "Sat"
69 }, {
70 "Sunday", "Monday", "Tuesday", "Wednesday",
71 "Thursday", "Friday", "Saturday"
72 },
73
74 /* X_fmt */
75 "%H:%M:%S",
76
77 /*
78 ** x_fmt
79 ** C99 and later require this format.
80 ** Using just numbers (as here) makes Quakers happier;
81 ** it's also compatible with SVR4.
82 */
83 "%m/%d/%y",
84
85 /*
86 ** c_fmt
87 ** C99 and later require this format.
88 ** Previously this code used "%D %X", but we now conform to C99.
89 ** Note that
90 ** "%a %b %d %H:%M:%S %Y"
91 ** is used by Solaris 2.3.
92 */
93 "%a %b %e %T %Y",
94
95 /* am */
96 "AM",
97
98 /* pm */
99 "PM",
100
101 /* date_fmt */
102 "%a %b %e %H:%M:%S %Z %Y"
103 };
104
105 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
106
107 static char * _add(const char *, char *, const char *);
108 static char * _conv(int, const char *, char *, const char *);
109 static char * _fmt(const char *, const struct tm *, char *, const char *,
110 enum warn *);
111 static char * _yconv(int, int, bool, bool, char *, char const *);
112
113 #ifndef YEAR_2000_NAME
114 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
115 #endif /* !defined YEAR_2000_NAME */
116
117 #if HAVE_STRFTIME_L
118 size_t
strftime_l(char * restrict s,size_t maxsize,char const * restrict format,struct tm const * restrict t,ATTRIBUTE_MAYBE_UNUSED locale_t locale)119 strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
120 struct tm const *restrict t,
121 ATTRIBUTE_MAYBE_UNUSED locale_t locale)
122 {
123 /* Just call strftime, as only the C locale is supported. */
124 return strftime(s, maxsize, format, t);
125 }
126 #endif
127
128 size_t
strftime(char * restrict s,size_t maxsize,char const * restrict format,struct tm const * restrict t)129 strftime(char *restrict s, size_t maxsize, char const *restrict format,
130 struct tm const *restrict t)
131 {
132 char * p;
133 int saved_errno = errno;
134 enum warn warn = IN_NONE;
135
136 tzset();
137 p = _fmt(format, t, s, s + maxsize, &warn);
138 if (!p) {
139 errno = EOVERFLOW;
140 return 0;
141 }
142 if (DEPRECATE_TWO_DIGIT_YEARS
143 && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
144 fprintf(stderr, "\n");
145 fprintf(stderr, "strftime format \"%s\" ", format);
146 fprintf(stderr, "yields only two digits of years in ");
147 if (warn == IN_SOME)
148 fprintf(stderr, "some locales");
149 else if (warn == IN_THIS)
150 fprintf(stderr, "the current locale");
151 else fprintf(stderr, "all locales");
152 fprintf(stderr, "\n");
153 }
154 if (p == s + maxsize) {
155 errno = ERANGE;
156 return 0;
157 }
158 *p = '\0';
159 errno = saved_errno;
160 return p - s;
161 }
162
163 static char *
_fmt(const char * format,const struct tm * t,char * pt,const char * ptlim,enum warn * warnp)164 _fmt(const char *format, const struct tm *t, char *pt,
165 const char *ptlim, enum warn *warnp)
166 {
167 struct lc_time_T const *Locale = &C_time_locale;
168
169 for ( ; *format; ++format) {
170 if (*format == '%') {
171 label:
172 switch (*++format) {
173 case '\0':
174 --format;
175 break;
176 case 'A':
177 pt = _add((t->tm_wday < 0 ||
178 t->tm_wday >= DAYSPERWEEK) ?
179 "?" : Locale->weekday[t->tm_wday],
180 pt, ptlim);
181 continue;
182 case 'a':
183 pt = _add((t->tm_wday < 0 ||
184 t->tm_wday >= DAYSPERWEEK) ?
185 "?" : Locale->wday[t->tm_wday],
186 pt, ptlim);
187 continue;
188 case 'B':
189 pt = _add((t->tm_mon < 0 ||
190 t->tm_mon >= MONSPERYEAR) ?
191 "?" : Locale->month[t->tm_mon],
192 pt, ptlim);
193 continue;
194 case 'b':
195 case 'h':
196 pt = _add((t->tm_mon < 0 ||
197 t->tm_mon >= MONSPERYEAR) ?
198 "?" : Locale->mon[t->tm_mon],
199 pt, ptlim);
200 continue;
201 case 'C':
202 /*
203 ** %C used to do a...
204 ** _fmt("%a %b %e %X %Y", t);
205 ** ...whereas now POSIX 1003.2 calls for
206 ** something completely different.
207 ** (ado, 1993-05-24)
208 */
209 pt = _yconv(t->tm_year, TM_YEAR_BASE,
210 true, false, pt, ptlim);
211 continue;
212 case 'c':
213 {
214 enum warn warn2 = IN_SOME;
215
216 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
217 if (warn2 == IN_ALL)
218 warn2 = IN_THIS;
219 if (warn2 > *warnp)
220 *warnp = warn2;
221 }
222 continue;
223 case 'D':
224 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
225 continue;
226 case 'd':
227 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
228 continue;
229 case 'E':
230 case 'O':
231 /*
232 ** Locale modifiers of C99 and later.
233 ** The sequences
234 ** %Ec %EC %Ex %EX %Ey %EY
235 ** %Od %oe %OH %OI %Om %OM
236 ** %OS %Ou %OU %OV %Ow %OW %Oy
237 ** are supposed to provide alternative
238 ** representations.
239 */
240 goto label;
241 case 'e':
242 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
243 continue;
244 case 'F':
245 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
246 continue;
247 case 'H':
248 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
249 continue;
250 case 'I':
251 pt = _conv((t->tm_hour % 12) ?
252 (t->tm_hour % 12) : 12,
253 "%02d", pt, ptlim);
254 continue;
255 case 'j':
256 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
257 continue;
258 case 'k':
259 /*
260 ** This used to be...
261 ** _conv(t->tm_hour % 12 ?
262 ** t->tm_hour % 12 : 12, 2, ' ');
263 ** ...and has been changed to the below to
264 ** match SunOS 4.1.1 and Arnold Robbins'
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, "%2d", pt, ptlim);
270 continue;
271 #ifdef KITCHEN_SINK
272 case 'K':
273 /*
274 ** After all this time, still unclaimed!
275 */
276 pt = _add("kitchen sink", pt, ptlim);
277 continue;
278 #endif /* defined KITCHEN_SINK */
279 case 'l':
280 /*
281 ** This used to be...
282 ** _conv(t->tm_hour, 2, ' ');
283 ** ...and has been changed to the below to
284 ** match SunOS 4.1.1 and Arnold Robbin's
285 ** strftime version 3.0. That is, "%k" and
286 ** "%l" have been swapped.
287 ** (ado, 1993-05-24)
288 */
289 pt = _conv((t->tm_hour % 12) ?
290 (t->tm_hour % 12) : 12,
291 "%2d", pt, ptlim);
292 continue;
293 case 'M':
294 pt = _conv(t->tm_min, "%02d", pt, ptlim);
295 continue;
296 case 'm':
297 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
298 continue;
299 case 'n':
300 pt = _add("\n", pt, ptlim);
301 continue;
302 case 'p':
303 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
304 Locale->pm :
305 Locale->am,
306 pt, ptlim);
307 continue;
308 case 'R':
309 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
310 continue;
311 case 'r':
312 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
313 continue;
314 case 'S':
315 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
316 continue;
317 case 's':
318 {
319 struct tm tm;
320 char buf[INT_STRLEN_MAXIMUM(
321 time_t) + 1];
322 time_t mkt;
323
324 tm.tm_sec = t->tm_sec;
325 tm.tm_min = t->tm_min;
326 tm.tm_hour = t->tm_hour;
327 tm.tm_mday = t->tm_mday;
328 tm.tm_mon = t->tm_mon;
329 tm.tm_year = t->tm_year;
330 #ifdef TM_GMTOFF
331 mkt = timeoff(&tm, t->TM_GMTOFF);
332 #else
333 tm.tm_isdst = t->tm_isdst;
334 mkt = mktime(&tm);
335 #endif
336 /* If mktime fails, %s expands to the
337 value of (time_t) -1 as a failure
338 marker; this is better in practice
339 than strftime failing. */
340 if (TYPE_SIGNED(time_t)) {
341 intmax_t n = mkt;
342 sprintf(buf, "%"PRIdMAX, n);
343 } else {
344 uintmax_t n = mkt;
345 sprintf(buf, "%"PRIuMAX, n);
346 }
347 pt = _add(buf, pt, ptlim);
348 }
349 continue;
350 case 'T':
351 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
352 continue;
353 case 't':
354 pt = _add("\t", pt, ptlim);
355 continue;
356 case 'U':
357 pt = _conv((t->tm_yday + DAYSPERWEEK -
358 t->tm_wday) / DAYSPERWEEK,
359 "%02d", pt, ptlim);
360 continue;
361 case 'u':
362 /*
363 ** From Arnold Robbins' strftime version 3.0:
364 ** "ISO 8601: Weekday as a decimal number
365 ** [1 (Monday) - 7]"
366 ** (ado, 1993-05-24)
367 */
368 pt = _conv((t->tm_wday == 0) ?
369 DAYSPERWEEK : t->tm_wday,
370 "%d", pt, ptlim);
371 continue;
372 case 'V': /* ISO 8601 week number */
373 case 'G': /* ISO 8601 year (four digits) */
374 case 'g': /* ISO 8601 year (two digits) */
375 /*
376 ** From Arnold Robbins' strftime version 3.0: "the week number of the
377 ** year (the first Monday as the first day of week 1) as a decimal number
378 ** (01-53)."
379 ** (ado, 1993-05-24)
380 **
381 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
382 ** "Week 01 of a year is per definition the first week which has the
383 ** Thursday in this year, which is equivalent to the week which contains
384 ** the fourth day of January. In other words, the first week of a new year
385 ** is the week which has the majority of its days in the new year. Week 01
386 ** might also contain days from the previous year and the week before week
387 ** 01 of a year is the last week (52 or 53) of the previous year even if
388 ** it contains days from the new year. A week starts with Monday (day 1)
389 ** and ends with Sunday (day 7). For example, the first week of the year
390 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
391 ** (ado, 1996-01-02)
392 */
393 {
394 int year;
395 int base;
396 int yday;
397 int wday;
398 int w;
399
400 year = t->tm_year;
401 base = TM_YEAR_BASE;
402 yday = t->tm_yday;
403 wday = t->tm_wday;
404 for ( ; ; ) {
405 int len;
406 int bot;
407 int top;
408
409 len = isleap_sum(year, base) ?
410 DAYSPERLYEAR :
411 DAYSPERNYEAR;
412 /*
413 ** What yday (-3 ... 3) does
414 ** the ISO year begin on?
415 */
416 bot = ((yday + 11 - wday) %
417 DAYSPERWEEK) - 3;
418 /*
419 ** What yday does the NEXT
420 ** ISO year begin on?
421 */
422 top = bot -
423 (len % DAYSPERWEEK);
424 if (top < -3)
425 top += DAYSPERWEEK;
426 top += len;
427 if (yday >= top) {
428 ++base;
429 w = 1;
430 break;
431 }
432 if (yday >= bot) {
433 w = 1 + ((yday - bot) /
434 DAYSPERWEEK);
435 break;
436 }
437 --base;
438 yday += isleap_sum(year, base) ?
439 DAYSPERLYEAR :
440 DAYSPERNYEAR;
441 }
442 #ifdef XPG4_1994_04_09
443 if ((w == 52 &&
444 t->tm_mon == TM_JANUARY) ||
445 (w == 1 &&
446 t->tm_mon == TM_DECEMBER))
447 w = 53;
448 #endif /* defined XPG4_1994_04_09 */
449 if (*format == 'V')
450 pt = _conv(w, "%02d",
451 pt, ptlim);
452 else if (*format == 'g') {
453 *warnp = IN_ALL;
454 pt = _yconv(year, base,
455 false, true,
456 pt, ptlim);
457 } else pt = _yconv(year, base,
458 true, true,
459 pt, ptlim);
460 }
461 continue;
462 case 'v':
463 /*
464 ** From Arnold Robbins' strftime version 3.0:
465 ** "date as dd-bbb-YYYY"
466 ** (ado, 1993-05-24)
467 */
468 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
469 continue;
470 case 'W':
471 pt = _conv((t->tm_yday + DAYSPERWEEK -
472 (t->tm_wday ?
473 (t->tm_wday - 1) :
474 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
475 "%02d", pt, ptlim);
476 continue;
477 case 'w':
478 pt = _conv(t->tm_wday, "%d", pt, ptlim);
479 continue;
480 case 'X':
481 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
482 continue;
483 case 'x':
484 {
485 enum warn warn2 = IN_SOME;
486
487 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
488 if (warn2 == IN_ALL)
489 warn2 = IN_THIS;
490 if (warn2 > *warnp)
491 *warnp = warn2;
492 }
493 continue;
494 case 'y':
495 *warnp = IN_ALL;
496 pt = _yconv(t->tm_year, TM_YEAR_BASE,
497 false, true,
498 pt, ptlim);
499 continue;
500 case 'Y':
501 pt = _yconv(t->tm_year, TM_YEAR_BASE,
502 true, true,
503 pt, ptlim);
504 continue;
505 case 'Z':
506 #ifdef TM_ZONE
507 pt = _add(t->TM_ZONE, pt, ptlim);
508 #elif HAVE_TZNAME
509 if (t->tm_isdst >= 0)
510 pt = _add(tzname[t->tm_isdst != 0],
511 pt, ptlim);
512 #endif
513 /*
514 ** C99 and later say that %Z must be
515 ** replaced by the empty string if the
516 ** time zone abbreviation is not
517 ** determinable.
518 */
519 continue;
520 case 'z':
521 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
522 {
523 long diff;
524 char const * sign;
525 bool negative;
526
527 # ifdef TM_GMTOFF
528 diff = t->TM_GMTOFF;
529 # else
530 /*
531 ** C99 and later say that the UT offset must
532 ** be computed by looking only at
533 ** tm_isdst. This requirement is
534 ** incorrect, since it means the code
535 ** must rely on magic (in this case
536 ** altzone and timezone), and the
537 ** magic might not have the correct
538 ** offset. Doing things correctly is
539 ** tricky and requires disobeying the standard;
540 ** see GNU C strftime for details.
541 ** For now, punt and conform to the
542 ** standard, even though it's incorrect.
543 **
544 ** C99 and later say that %z must be replaced by
545 ** the empty string if the time zone is not
546 ** determinable, so output nothing if the
547 ** appropriate variables are not available.
548 */
549 if (t->tm_isdst < 0)
550 continue;
551 if (t->tm_isdst == 0)
552 # if USG_COMPAT
553 diff = -timezone;
554 # else
555 continue;
556 # endif
557 else
558 # if ALTZONE
559 diff = -altzone;
560 # else
561 continue;
562 # endif
563 # endif
564 negative = diff < 0;
565 if (diff == 0) {
566 # ifdef TM_ZONE
567 negative = t->TM_ZONE[0] == '-';
568 # else
569 negative = t->tm_isdst < 0;
570 # if HAVE_TZNAME
571 if (tzname[t->tm_isdst != 0][0] == '-')
572 negative = true;
573 # endif
574 # endif
575 }
576 if (negative) {
577 sign = "-";
578 diff = -diff;
579 } else sign = "+";
580 pt = _add(sign, pt, ptlim);
581 diff /= SECSPERMIN;
582 diff = (diff / MINSPERHOUR) * 100 +
583 (diff % MINSPERHOUR);
584 pt = _conv(diff, "%04d", pt, ptlim);
585 }
586 #endif
587 continue;
588 case '+':
589 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
590 warnp);
591 continue;
592 case '%':
593 /*
594 ** X311J/88-090 (4.12.3.5): if conversion char is
595 ** undefined, behavior is undefined. Print out the
596 ** character itself as printf(3) also does.
597 */
598 default:
599 break;
600 }
601 }
602 if (pt == ptlim)
603 break;
604 *pt++ = *format;
605 }
606 return pt;
607 }
608
609 static char *
_conv(int n,const char * format,char * pt,const char * ptlim)610 _conv(int n, const char *format, char *pt, const char *ptlim)
611 {
612 char buf[INT_STRLEN_MAXIMUM(int) + 1];
613
614 sprintf(buf, format, n);
615 return _add(buf, pt, ptlim);
616 }
617
618 static char *
_add(const char * str,char * pt,const char * ptlim)619 _add(const char *str, char *pt, const char *ptlim)
620 {
621 while (pt < ptlim && (*pt = *str++) != '\0')
622 ++pt;
623 return pt;
624 }
625
626 /*
627 ** POSIX and the C Standard are unclear or inconsistent about
628 ** what %C and %y do if the year is negative or exceeds 9999.
629 ** Use the convention that %C concatenated with %y yields the
630 ** same output as %Y, and that %Y contains at least 4 bytes,
631 ** with more only if necessary.
632 */
633
634 static char *
_yconv(int a,int b,bool convert_top,bool convert_yy,char * pt,const char * ptlim)635 _yconv(int a, int b, bool convert_top, bool convert_yy,
636 char *pt, const char *ptlim)
637 {
638 register int lead;
639 register int trail;
640
641 int DIVISOR = 100;
642 trail = a % DIVISOR + b % DIVISOR;
643 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
644 trail %= DIVISOR;
645 if (trail < 0 && lead > 0) {
646 trail += DIVISOR;
647 --lead;
648 } else if (lead < 0 && trail > 0) {
649 trail -= DIVISOR;
650 ++lead;
651 }
652 if (convert_top) {
653 if (lead == 0 && trail < 0)
654 pt = _add("-0", pt, ptlim);
655 else pt = _conv(lead, "%02d", pt, ptlim);
656 }
657 if (convert_yy)
658 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
659 return pt;
660 }
661