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