1 /*
2 * SPDX-License-Identifier: BSD-4.3TAHOE
3 *
4 * Copyright (c) 1989 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * Copyright (c) 2011 The FreeBSD Foundation
8 *
9 * Portions of this software were developed by David Chisnall
10 * under sponsorship from the FreeBSD Foundation.
11 *
12 * Redistribution and use in source and binary forms are permitted
13 * provided that the above copyright notice and this paragraph are
14 * duplicated in all such forms and that any documentation,
15 * advertising materials, and other materials related to such
16 * distribution and use acknowledge that the software was developed
17 * by the University of California, Berkeley. The name of the
18 * University may not be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 */
24
25 #include "namespace.h"
26 #include "private.h"
27
28 #include "tzfile.h"
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <stdio.h>
32 #include "un-namespace.h"
33 #include "timelocal.h"
34
35 static char * _add(const char *, char *, const char *);
36 static char * _conv(int, const char *, char *, const char *, locale_t);
37 static char * _fmt(const char *, const struct tm *, char *, const char *,
38 int *, locale_t);
39 static char * _yconv(int, int, int, int, char *, const char *, locale_t);
40
41 extern char * tzname[];
42
43 #ifndef YEAR_2000_NAME
44 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
45 #endif /* !defined YEAR_2000_NAME */
46
47 #define IN_NONE 0
48 #define IN_SOME 1
49 #define IN_THIS 2
50 #define IN_ALL 3
51
52 #define PAD_DEFAULT 0
53 #define PAD_LESS 1
54 #define PAD_SPACE 2
55 #define PAD_ZERO 3
56
57 static const char fmt_padding[][4][5] = {
58 /* DEFAULT, LESS, SPACE, ZERO */
59 #define PAD_FMT_MONTHDAY 0
60 #define PAD_FMT_HMS 0
61 #define PAD_FMT_CENTURY 0
62 #define PAD_FMT_SHORTYEAR 0
63 #define PAD_FMT_MONTH 0
64 #define PAD_FMT_WEEKOFYEAR 0
65 #define PAD_FMT_DAYOFMONTH 0
66 { "%02d", "%d", "%2d", "%02d" },
67 #define PAD_FMT_SDAYOFMONTH 1
68 #define PAD_FMT_SHMS 1
69 { "%2d", "%d", "%2d", "%02d" },
70 #define PAD_FMT_DAYOFYEAR 2
71 { "%03d", "%d", "%3d", "%03d" },
72 #define PAD_FMT_YEAR 3
73 { "%04d", "%d", "%4d", "%04d" }
74 };
75
76 size_t
strftime_l(char * __restrict s,size_t maxsize,const char * __restrict format,const struct tm * __restrict t,locale_t loc)77 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
78 const struct tm * __restrict t, locale_t loc)
79 {
80 char * p;
81 int warn;
82 FIX_LOCALE(loc);
83
84 tzset();
85 warn = IN_NONE;
86 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn, loc);
87 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
88 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
89 (void) fprintf_l(stderr, loc, "\n");
90 if (format == NULL)
91 (void) fputs("NULL strftime format ", stderr);
92 else (void) fprintf_l(stderr, loc, "strftime format \"%s\" ",
93 format);
94 (void) fputs("yields only two digits of years in ", stderr);
95 if (warn == IN_SOME)
96 (void) fputs("some locales", stderr);
97 else if (warn == IN_THIS)
98 (void) fputs("the current locale", stderr);
99 else (void) fputs("all locales", stderr);
100 (void) fputs("\n", stderr);
101 }
102 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
103 if (p == s + maxsize)
104 return (0);
105 *p = '\0';
106 return p - s;
107 }
108
109 size_t
strftime(char * __restrict s,size_t maxsize,const char * __restrict format,const struct tm * __restrict t)110 strftime(char * __restrict s, size_t maxsize, const char * __restrict format,
111 const struct tm * __restrict t)
112 {
113 return strftime_l(s, maxsize, format, t, __get_locale());
114 }
115
116 static char *
_fmt(const char * format,const struct tm * const t,char * pt,const char * const ptlim,int * warnp,locale_t loc)117 _fmt(const char *format, const struct tm * const t, char *pt,
118 const char * const ptlim, int *warnp, locale_t loc)
119 {
120 int Ealternative, Oalternative, PadIndex;
121 struct lc_time_T *tptr = __get_current_time_locale(loc);
122
123 for ( ; *format; ++format) {
124 if (*format == '%') {
125 Ealternative = 0;
126 Oalternative = 0;
127 PadIndex = PAD_DEFAULT;
128 label:
129 switch (*++format) {
130 case '\0':
131 --format;
132 break;
133 case 'A':
134 pt = _add((t->tm_wday < 0 ||
135 t->tm_wday >= DAYSPERWEEK) ?
136 "?" : tptr->weekday[t->tm_wday],
137 pt, ptlim);
138 continue;
139 case 'a':
140 pt = _add((t->tm_wday < 0 ||
141 t->tm_wday >= DAYSPERWEEK) ?
142 "?" : tptr->wday[t->tm_wday],
143 pt, ptlim);
144 continue;
145 case 'B':
146 pt = _add((t->tm_mon < 0 ||
147 t->tm_mon >= MONSPERYEAR) ?
148 "?" : (Oalternative ? tptr->alt_month :
149 tptr->month)[t->tm_mon],
150 pt, ptlim);
151 continue;
152 case 'b':
153 case 'h':
154 pt = _add((t->tm_mon < 0 ||
155 t->tm_mon >= MONSPERYEAR) ?
156 "?" : tptr->mon[t->tm_mon],
157 pt, ptlim);
158 continue;
159 case 'C':
160 /*
161 * %C used to do a...
162 * _fmt("%a %b %e %X %Y", t);
163 * ...whereas now POSIX 1003.2 calls for
164 * something completely different.
165 * (ado, 1993-05-24)
166 */
167 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
168 pt, ptlim, loc);
169 continue;
170 case 'c':
171 {
172 int warn2 = IN_SOME;
173
174 pt = _fmt(tptr->c_fmt, t, pt, ptlim, &warn2, loc);
175 if (warn2 == IN_ALL)
176 warn2 = IN_THIS;
177 if (warn2 > *warnp)
178 *warnp = warn2;
179 }
180 continue;
181 case 'D':
182 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, loc);
183 continue;
184 case 'd':
185 pt = _conv(t->tm_mday,
186 fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
187 pt, ptlim, loc);
188 continue;
189 case 'E':
190 if (Ealternative || Oalternative)
191 break;
192 Ealternative++;
193 goto label;
194 case 'O':
195 /*
196 * C99 locale modifiers.
197 * The sequences
198 * %Ec %EC %Ex %EX %Ey %EY
199 * %Od %oe %OH %OI %Om %OM
200 * %OS %Ou %OU %OV %Ow %OW %Oy
201 * are supposed to provide alternate
202 * representations.
203 *
204 * FreeBSD extension
205 * %OB
206 */
207 if (Ealternative || Oalternative)
208 break;
209 Oalternative++;
210 goto label;
211 case 'e':
212 pt = _conv(t->tm_mday,
213 fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
214 pt, ptlim, loc);
215 continue;
216 case 'F':
217 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, loc);
218 continue;
219 case 'H':
220 pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_HMS][PadIndex],
221 pt, ptlim, loc);
222 continue;
223 case 'I':
224 pt = _conv((t->tm_hour % 12) ?
225 (t->tm_hour % 12) : 12,
226 fmt_padding[PAD_FMT_HMS][PadIndex],
227 pt, ptlim, loc);
228 continue;
229 case 'j':
230 pt = _conv(t->tm_yday + 1,
231 fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
232 pt, ptlim, loc);
233 continue;
234 case 'k':
235 /*
236 * This used to be...
237 * _conv(t->tm_hour % 12 ?
238 * t->tm_hour % 12 : 12, 2, ' ');
239 * ...and has been changed to the below to
240 * match SunOS 4.1.1 and Arnold Robbins'
241 * strftime version 3.0. That is, "%k" and
242 * "%l" have been swapped.
243 * (ado, 1993-05-24)
244 */
245 pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_SHMS][PadIndex],
246 pt, ptlim, loc);
247 continue;
248 #ifdef KITCHEN_SINK
249 case 'K':
250 /*
251 ** After all this time, still unclaimed!
252 */
253 pt = _add("kitchen sink", pt, ptlim);
254 continue;
255 #endif /* defined KITCHEN_SINK */
256 case 'l':
257 /*
258 * This used to be...
259 * _conv(t->tm_hour, 2, ' ');
260 * ...and has been changed to the below to
261 * match SunOS 4.1.1 and Arnold Robbin's
262 * strftime version 3.0. That is, "%k" and
263 * "%l" have been swapped.
264 * (ado, 1993-05-24)
265 */
266 pt = _conv((t->tm_hour % 12) ?
267 (t->tm_hour % 12) : 12,
268 fmt_padding[PAD_FMT_SHMS][PadIndex],
269 pt, ptlim, loc);
270 continue;
271 case 'M':
272 pt = _conv(t->tm_min, fmt_padding[PAD_FMT_HMS][PadIndex],
273 pt, ptlim, loc);
274 continue;
275 case 'm':
276 pt = _conv(t->tm_mon + 1,
277 fmt_padding[PAD_FMT_MONTH][PadIndex],
278 pt, ptlim, loc);
279 continue;
280 case 'n':
281 pt = _add("\n", pt, ptlim);
282 continue;
283 case 'p':
284 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
285 tptr->pm : tptr->am,
286 pt, ptlim);
287 continue;
288 case 'R':
289 pt = _fmt("%H:%M", t, pt, ptlim, warnp, loc);
290 continue;
291 case 'r':
292 pt = _fmt(tptr->ampm_fmt, t, pt, ptlim,
293 warnp, loc);
294 continue;
295 case 'S':
296 pt = _conv(t->tm_sec, fmt_padding[PAD_FMT_HMS][PadIndex],
297 pt, ptlim, loc);
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 = timeoff(&tm, t->tm_gmtoff);
308 if (TYPE_SIGNED(time_t))
309 (void) sprintf_l(buf, loc, "%ld",
310 (long) mkt);
311 else (void) sprintf_l(buf, loc, "%lu",
312 (unsigned long) mkt);
313 pt = _add(buf, pt, ptlim);
314 }
315 continue;
316 case 'T':
317 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, loc);
318 continue;
319 case 't':
320 pt = _add("\t", pt, ptlim);
321 continue;
322 case 'U':
323 pt = _conv((t->tm_yday + DAYSPERWEEK -
324 t->tm_wday) / DAYSPERWEEK,
325 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
326 pt, ptlim, loc);
327 continue;
328 case 'u':
329 /*
330 * From Arnold Robbins' strftime version 3.0:
331 * "ISO 8601: Weekday as a decimal number
332 * [1 (Monday) - 7]"
333 * (ado, 1993-05-24)
334 */
335 pt = _conv((t->tm_wday == 0) ?
336 DAYSPERWEEK : t->tm_wday,
337 "%d", pt, ptlim, loc);
338 continue;
339 case 'V': /* ISO 8601 week number */
340 case 'G': /* ISO 8601 year (four digits) */
341 case 'g': /* ISO 8601 year (two digits) */
342 /*
343 * From Arnold Robbins' strftime version 3.0: "the week number of the
344 * year (the first Monday as the first day of week 1) as a decimal number
345 * (01-53)."
346 * (ado, 1993-05-24)
347 *
348 * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
349 * "Week 01 of a year is per definition the first week which has the
350 * Thursday in this year, which is equivalent to the week which contains
351 * the fourth day of January. In other words, the first week of a new year
352 * is the week which has the majority of its days in the new year. Week 01
353 * might also contain days from the previous year and the week before week
354 * 01 of a year is the last week (52 or 53) of the previous year even if
355 * it contains days from the new year. A week starts with Monday (day 1)
356 * and ends with Sunday (day 7). For example, the first week of the year
357 * 1997 lasts from 1996-12-30 to 1997-01-05..."
358 * (ado, 1996-01-02)
359 */
360 {
361 int year;
362 int base;
363 int yday;
364 int wday;
365 int w;
366
367 year = t->tm_year;
368 base = TM_YEAR_BASE;
369 yday = t->tm_yday;
370 wday = t->tm_wday;
371 for ( ; ; ) {
372 int len;
373 int bot;
374 int top;
375
376 len = isleap_sum(year, base) ?
377 DAYSPERLYEAR :
378 DAYSPERNYEAR;
379 /*
380 * What yday (-3 ... 3) does
381 * the ISO year begin on?
382 */
383 bot = ((yday + 11 - wday) %
384 DAYSPERWEEK) - 3;
385 /*
386 * What yday does the NEXT
387 * ISO year begin on?
388 */
389 top = bot -
390 (len % DAYSPERWEEK);
391 if (top < -3)
392 top += DAYSPERWEEK;
393 top += len;
394 if (yday >= top) {
395 ++base;
396 w = 1;
397 break;
398 }
399 if (yday >= bot) {
400 w = 1 + ((yday - bot) /
401 DAYSPERWEEK);
402 break;
403 }
404 --base;
405 yday += isleap_sum(year, base) ?
406 DAYSPERLYEAR :
407 DAYSPERNYEAR;
408 }
409 #ifdef XPG4_1994_04_09
410 if ((w == 52 &&
411 t->tm_mon == TM_JANUARY) ||
412 (w == 1 &&
413 t->tm_mon == TM_DECEMBER))
414 w = 53;
415 #endif /* defined XPG4_1994_04_09 */
416 if (*format == 'V')
417 pt = _conv(w, fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
418 pt, ptlim, loc);
419 else if (*format == 'g') {
420 *warnp = IN_ALL;
421 pt = _yconv(year, base, 0, 1,
422 pt, ptlim, loc);
423 } else pt = _yconv(year, base, 1, 1,
424 pt, ptlim, loc);
425 }
426 continue;
427 case 'v':
428 /*
429 * From Arnold Robbins' strftime version 3.0:
430 * "date as dd-bbb-YYYY"
431 * (ado, 1993-05-24)
432 */
433 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, loc);
434 continue;
435 case 'W':
436 pt = _conv((t->tm_yday + DAYSPERWEEK -
437 (t->tm_wday ?
438 (t->tm_wday - 1) :
439 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
440 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
441 pt, ptlim, loc);
442 continue;
443 case 'w':
444 pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
445 continue;
446 case 'X':
447 pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp, loc);
448 continue;
449 case 'x':
450 {
451 int warn2 = IN_SOME;
452
453 pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2, loc);
454 if (warn2 == IN_ALL)
455 warn2 = IN_THIS;
456 if (warn2 > *warnp)
457 *warnp = warn2;
458 }
459 continue;
460 case 'y':
461 *warnp = IN_ALL;
462 pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
463 pt, ptlim, loc);
464 continue;
465 case 'Y':
466 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
467 pt, ptlim, loc);
468 continue;
469 case 'Z':
470 #ifdef TM_ZONE
471 if (t->TM_ZONE != NULL)
472 pt = _add(t->TM_ZONE, pt, ptlim);
473 else
474 #endif /* defined TM_ZONE */
475 if (t->tm_isdst >= 0)
476 pt = _add(tzname[t->tm_isdst != 0],
477 pt, ptlim);
478 /*
479 * C99 says that %Z must be replaced by the
480 * empty string if the time zone is not
481 * determinable.
482 */
483 continue;
484 case 'z':
485 {
486 int diff;
487 char const * sign;
488
489 if (t->tm_isdst < 0)
490 continue;
491 #ifdef TM_GMTOFF
492 diff = t->TM_GMTOFF;
493 #else /* !defined TM_GMTOFF */
494 /*
495 * C99 says that the UTC offset must
496 * be computed by looking only at
497 * tm_isdst. This requirement is
498 * incorrect, since it means the code
499 * must rely on magic (in this case
500 * altzone and timezone), and the
501 * magic might not have the correct
502 * offset. Doing things correctly is
503 * tricky and requires disobeying C99;
504 * see GNU C strftime for details.
505 * For now, punt and conform to the
506 * standard, even though it's incorrect.
507 *
508 * C99 says that %z must be replaced by the
509 * empty string if the time zone is not
510 * determinable, so output nothing if the
511 * appropriate variables are not available.
512 */
513 if (t->tm_isdst == 0)
514 #ifdef USG_COMPAT
515 diff = -timezone;
516 #else /* !defined USG_COMPAT */
517 continue;
518 #endif /* !defined USG_COMPAT */
519 else
520 #ifdef ALTZONE
521 diff = -altzone;
522 #else /* !defined ALTZONE */
523 continue;
524 #endif /* !defined ALTZONE */
525 #endif /* !defined TM_GMTOFF */
526 if (diff < 0) {
527 sign = "-";
528 diff = -diff;
529 } else
530 sign = "+";
531 pt = _add(sign, pt, ptlim);
532 diff /= SECSPERMIN;
533 diff = (diff / MINSPERHOUR) * 100 +
534 (diff % MINSPERHOUR);
535 pt = _conv(diff,
536 fmt_padding[PAD_FMT_YEAR][PadIndex],
537 pt, ptlim, loc);
538 }
539 continue;
540 case '+':
541 pt = _fmt(tptr->date_fmt, t, pt, ptlim,
542 warnp, loc);
543 continue;
544 case '-':
545 if (PadIndex != PAD_DEFAULT)
546 break;
547 PadIndex = PAD_LESS;
548 goto label;
549 case '_':
550 if (PadIndex != PAD_DEFAULT)
551 break;
552 PadIndex = PAD_SPACE;
553 goto label;
554 case '0':
555 if (PadIndex != PAD_DEFAULT)
556 break;
557 PadIndex = PAD_ZERO;
558 goto label;
559 case '%':
560 /*
561 * X311J/88-090 (4.12.3.5): if conversion char is
562 * undefined, behavior is undefined. Print out the
563 * character itself as printf(3) also does.
564 */
565 default:
566 break;
567 }
568 }
569 if (pt == ptlim)
570 break;
571 *pt++ = *format;
572 }
573 return (pt);
574 }
575
576 static char *
_conv(const int n,const char * const format,char * const pt,const char * const ptlim,locale_t loc)577 _conv(const int n, const char * const format, char * const pt,
578 const char * const ptlim, locale_t loc)
579 {
580 char buf[INT_STRLEN_MAXIMUM(int) + 1];
581
582 (void) sprintf_l(buf, loc, format, n);
583 return _add(buf, pt, ptlim);
584 }
585
586 static char *
_add(const char * str,char * pt,const char * const ptlim)587 _add(const char *str, char *pt, const char * const ptlim)
588 {
589 while (pt < ptlim && (*pt = *str++) != '\0')
590 ++pt;
591 return (pt);
592 }
593
594 /*
595 * POSIX and the C Standard are unclear or inconsistent about
596 * what %C and %y do if the year is negative or exceeds 9999.
597 * Use the convention that %C concatenated with %y yields the
598 * same output as %Y, and that %Y contains at least 4 bytes,
599 * with more only if necessary.
600 */
601
602 static char *
_yconv(const int a,const int b,const int convert_top,const int convert_yy,char * pt,const char * const ptlim,locale_t loc)603 _yconv(const int a, const int b, const int convert_top, const int convert_yy,
604 char *pt, const char * const ptlim, locale_t loc)
605 {
606 register int lead;
607 register int trail;
608
609 #define DIVISOR 100
610 trail = a % DIVISOR + b % DIVISOR;
611 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
612 trail %= DIVISOR;
613 if (trail < 0 && lead > 0) {
614 trail += DIVISOR;
615 --lead;
616 } else if (lead < 0 && trail > 0) {
617 trail -= DIVISOR;
618 ++lead;
619 }
620 if (convert_top) {
621 if (lead == 0 && trail < 0)
622 pt = _add("-0", pt, ptlim);
623 else pt = _conv(lead, "%02d", pt, ptlim, loc);
624 }
625 if (convert_yy)
626 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt,
627 ptlim, loc);
628 return (pt);
629 }
630