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 sprintf(buf, "%"PRIdMAX,
340 (intmax_t) mkt);
341 else sprintf(buf, "%"PRIuMAX,
342 (uintmax_t) mkt);
343 pt = _add(buf, pt, ptlim);
344 }
345 continue;
346 case 'T':
347 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
348 continue;
349 case 't':
350 pt = _add("\t", pt, ptlim);
351 continue;
352 case 'U':
353 pt = _conv((t->tm_yday + DAYSPERWEEK -
354 t->tm_wday) / DAYSPERWEEK,
355 "%02d", pt, ptlim);
356 continue;
357 case 'u':
358 /*
359 ** From Arnold Robbins' strftime version 3.0:
360 ** "ISO 8601: Weekday as a decimal number
361 ** [1 (Monday) - 7]"
362 ** (ado, 1993-05-24)
363 */
364 pt = _conv((t->tm_wday == 0) ?
365 DAYSPERWEEK : t->tm_wday,
366 "%d", pt, ptlim);
367 continue;
368 case 'V': /* ISO 8601 week number */
369 case 'G': /* ISO 8601 year (four digits) */
370 case 'g': /* ISO 8601 year (two digits) */
371 /*
372 ** From Arnold Robbins' strftime version 3.0: "the week number of the
373 ** year (the first Monday as the first day of week 1) as a decimal number
374 ** (01-53)."
375 ** (ado, 1993-05-24)
376 **
377 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
378 ** "Week 01 of a year is per definition the first week which has the
379 ** Thursday in this year, which is equivalent to the week which contains
380 ** the fourth day of January. In other words, the first week of a new year
381 ** is the week which has the majority of its days in the new year. Week 01
382 ** might also contain days from the previous year and the week before week
383 ** 01 of a year is the last week (52 or 53) of the previous year even if
384 ** it contains days from the new year. A week starts with Monday (day 1)
385 ** and ends with Sunday (day 7). For example, the first week of the year
386 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
387 ** (ado, 1996-01-02)
388 */
389 {
390 int year;
391 int base;
392 int yday;
393 int wday;
394 int w;
395
396 year = t->tm_year;
397 base = TM_YEAR_BASE;
398 yday = t->tm_yday;
399 wday = t->tm_wday;
400 for ( ; ; ) {
401 int len;
402 int bot;
403 int top;
404
405 len = isleap_sum(year, base) ?
406 DAYSPERLYEAR :
407 DAYSPERNYEAR;
408 /*
409 ** What yday (-3 ... 3) does
410 ** the ISO year begin on?
411 */
412 bot = ((yday + 11 - wday) %
413 DAYSPERWEEK) - 3;
414 /*
415 ** What yday does the NEXT
416 ** ISO year begin on?
417 */
418 top = bot -
419 (len % DAYSPERWEEK);
420 if (top < -3)
421 top += DAYSPERWEEK;
422 top += len;
423 if (yday >= top) {
424 ++base;
425 w = 1;
426 break;
427 }
428 if (yday >= bot) {
429 w = 1 + ((yday - bot) /
430 DAYSPERWEEK);
431 break;
432 }
433 --base;
434 yday += isleap_sum(year, base) ?
435 DAYSPERLYEAR :
436 DAYSPERNYEAR;
437 }
438 #ifdef XPG4_1994_04_09
439 if ((w == 52 &&
440 t->tm_mon == TM_JANUARY) ||
441 (w == 1 &&
442 t->tm_mon == TM_DECEMBER))
443 w = 53;
444 #endif /* defined XPG4_1994_04_09 */
445 if (*format == 'V')
446 pt = _conv(w, "%02d",
447 pt, ptlim);
448 else if (*format == 'g') {
449 *warnp = IN_ALL;
450 pt = _yconv(year, base,
451 false, true,
452 pt, ptlim);
453 } else pt = _yconv(year, base,
454 true, true,
455 pt, ptlim);
456 }
457 continue;
458 case 'v':
459 /*
460 ** From Arnold Robbins' strftime version 3.0:
461 ** "date as dd-bbb-YYYY"
462 ** (ado, 1993-05-24)
463 */
464 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
465 continue;
466 case 'W':
467 pt = _conv((t->tm_yday + DAYSPERWEEK -
468 (t->tm_wday ?
469 (t->tm_wday - 1) :
470 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
471 "%02d", pt, ptlim);
472 continue;
473 case 'w':
474 pt = _conv(t->tm_wday, "%d", pt, ptlim);
475 continue;
476 case 'X':
477 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
478 continue;
479 case 'x':
480 {
481 enum warn warn2 = IN_SOME;
482
483 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
484 if (warn2 == IN_ALL)
485 warn2 = IN_THIS;
486 if (warn2 > *warnp)
487 *warnp = warn2;
488 }
489 continue;
490 case 'y':
491 *warnp = IN_ALL;
492 pt = _yconv(t->tm_year, TM_YEAR_BASE,
493 false, true,
494 pt, ptlim);
495 continue;
496 case 'Y':
497 pt = _yconv(t->tm_year, TM_YEAR_BASE,
498 true, true,
499 pt, ptlim);
500 continue;
501 case 'Z':
502 #ifdef TM_ZONE
503 pt = _add(t->TM_ZONE, pt, ptlim);
504 #elif HAVE_TZNAME
505 if (t->tm_isdst >= 0)
506 pt = _add(tzname[t->tm_isdst != 0],
507 pt, ptlim);
508 #endif
509 /*
510 ** C99 and later say that %Z must be
511 ** replaced by the empty string if the
512 ** time zone abbreviation is not
513 ** determinable.
514 */
515 continue;
516 case 'z':
517 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
518 {
519 long diff;
520 char const * sign;
521 bool negative;
522
523 # ifdef TM_GMTOFF
524 diff = t->TM_GMTOFF;
525 # else
526 /*
527 ** C99 and later say that the UT offset must
528 ** be computed by looking only at
529 ** tm_isdst. This requirement is
530 ** incorrect, since it means the code
531 ** must rely on magic (in this case
532 ** altzone and timezone), and the
533 ** magic might not have the correct
534 ** offset. Doing things correctly is
535 ** tricky and requires disobeying the standard;
536 ** see GNU C strftime for details.
537 ** For now, punt and conform to the
538 ** standard, even though it's incorrect.
539 **
540 ** C99 and later say that %z must be replaced by
541 ** the empty string if the time zone is not
542 ** determinable, so output nothing if the
543 ** appropriate variables are not available.
544 */
545 if (t->tm_isdst < 0)
546 continue;
547 if (t->tm_isdst == 0)
548 # if USG_COMPAT
549 diff = -timezone;
550 # else
551 continue;
552 # endif
553 else
554 # if ALTZONE
555 diff = -altzone;
556 # else
557 continue;
558 # endif
559 # endif
560 negative = diff < 0;
561 if (diff == 0) {
562 #ifdef TM_ZONE
563 negative = t->TM_ZONE[0] == '-';
564 #else
565 negative = t->tm_isdst < 0;
566 # if HAVE_TZNAME
567 if (tzname[t->tm_isdst != 0][0] == '-')
568 negative = true;
569 # endif
570 #endif
571 }
572 if (negative) {
573 sign = "-";
574 diff = -diff;
575 } else sign = "+";
576 pt = _add(sign, pt, ptlim);
577 diff /= SECSPERMIN;
578 diff = (diff / MINSPERHOUR) * 100 +
579 (diff % MINSPERHOUR);
580 pt = _conv(diff, "%04d", pt, ptlim);
581 }
582 #endif
583 continue;
584 case '+':
585 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
586 warnp);
587 continue;
588 case '%':
589 /*
590 ** X311J/88-090 (4.12.3.5): if conversion char is
591 ** undefined, behavior is undefined. Print out the
592 ** character itself as printf(3) also does.
593 */
594 default:
595 break;
596 }
597 }
598 if (pt == ptlim)
599 break;
600 *pt++ = *format;
601 }
602 return pt;
603 }
604
605 static char *
_conv(int n,const char * format,char * pt,const char * ptlim)606 _conv(int n, const char *format, char *pt, const char *ptlim)
607 {
608 char buf[INT_STRLEN_MAXIMUM(int) + 1];
609
610 sprintf(buf, format, n);
611 return _add(buf, pt, ptlim);
612 }
613
614 static char *
_add(const char * str,char * pt,const char * ptlim)615 _add(const char *str, char *pt, const char *ptlim)
616 {
617 while (pt < ptlim && (*pt = *str++) != '\0')
618 ++pt;
619 return pt;
620 }
621
622 /*
623 ** POSIX and the C Standard are unclear or inconsistent about
624 ** what %C and %y do if the year is negative or exceeds 9999.
625 ** Use the convention that %C concatenated with %y yields the
626 ** same output as %Y, and that %Y contains at least 4 bytes,
627 ** with more only if necessary.
628 */
629
630 static char *
_yconv(int a,int b,bool convert_top,bool convert_yy,char * pt,const char * ptlim)631 _yconv(int a, int b, bool convert_top, bool convert_yy,
632 char *pt, const char *ptlim)
633 {
634 register int lead;
635 register int trail;
636
637 #define DIVISOR 100
638 trail = a % DIVISOR + b % DIVISOR;
639 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
640 trail %= DIVISOR;
641 if (trail < 0 && lead > 0) {
642 trail += DIVISOR;
643 --lead;
644 } else if (lead < 0 && trail > 0) {
645 trail -= DIVISOR;
646 ++lead;
647 }
648 if (convert_top) {
649 if (lead == 0 && trail < 0)
650 pt = _add("-0", pt, ptlim);
651 else pt = _conv(lead, "%02d", pt, ptlim);
652 }
653 if (convert_yy)
654 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
655 return pt;
656 }
657