"Fossies" - the Fresh Open Source Software Archive 
Member "strftime.c" (20 Nov 2022, 16725 Bytes) of package /linux/misc/tzcode2022g.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "strftime.c":
2022f_vs_2022g.
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
119 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
120 ATTRIBUTE_MAYBE_UNUSED locale_t locale)
121 {
122 /* Just call strftime, as only the C locale is supported. */
123 return strftime(s, maxsize, format, t);
124 }
125 #endif
126
127 size_t
128 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
129 {
130 char * p;
131 int saved_errno = errno;
132 enum warn warn = IN_NONE;
133
134 tzset();
135 p = _fmt(format, t, s, s + maxsize, &warn);
136 if (!p) {
137 errno = EOVERFLOW;
138 return 0;
139 }
140 if (DEPRECATE_TWO_DIGIT_YEARS
141 && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
142 fprintf(stderr, "\n");
143 fprintf(stderr, "strftime format \"%s\" ", format);
144 fprintf(stderr, "yields only two digits of years in ");
145 if (warn == IN_SOME)
146 fprintf(stderr, "some locales");
147 else if (warn == IN_THIS)
148 fprintf(stderr, "the current locale");
149 else fprintf(stderr, "all locales");
150 fprintf(stderr, "\n");
151 }
152 if (p == s + maxsize) {
153 errno = ERANGE;
154 return 0;
155 }
156 *p = '\0';
157 errno = saved_errno;
158 return p - s;
159 }
160
161 static char *
162 _fmt(const char *format, const struct tm *t, char *pt,
163 const char *ptlim, enum warn *warnp)
164 {
165 struct lc_time_T const *Locale = &C_time_locale;
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.tm_sec = t->tm_sec;
323 tm.tm_min = t->tm_min;
324 tm.tm_hour = t->tm_hour;
325 tm.tm_mday = t->tm_mday;
326 tm.tm_mon = t->tm_mon;
327 tm.tm_year = t->tm_year;
328 tm.tm_isdst = t->tm_isdst;
329 #if defined TM_GMTOFF && ! UNINIT_TRAP
330 tm.TM_GMTOFF = t->TM_GMTOFF;
331 #endif
332 mkt = mktime(&tm);
333 /* If mktime fails, %s expands to the
334 value of (time_t) -1 as a failure
335 marker; this is better in practice
336 than strftime failing. */
337 if (TYPE_SIGNED(time_t)) {
338 intmax_t n = mkt;
339 sprintf(buf, "%"PRIdMAX, n);
340 } else {
341 uintmax_t n = mkt;
342 sprintf(buf, "%"PRIuMAX, n);
343 }
344 pt = _add(buf, pt, ptlim);
345 }
346 continue;
347 case 'T':
348 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
349 continue;
350 case 't':
351 pt = _add("\t", pt, ptlim);
352 continue;
353 case 'U':
354 pt = _conv((t->tm_yday + DAYSPERWEEK -
355 t->tm_wday) / DAYSPERWEEK,
356 "%02d", pt, ptlim);
357 continue;
358 case 'u':
359 /*
360 ** From Arnold Robbins' strftime version 3.0:
361 ** "ISO 8601: Weekday as a decimal number
362 ** [1 (Monday) - 7]"
363 ** (ado, 1993-05-24)
364 */
365 pt = _conv((t->tm_wday == 0) ?
366 DAYSPERWEEK : t->tm_wday,
367 "%d", pt, ptlim);
368 continue;
369 case 'V': /* ISO 8601 week number */
370 case 'G': /* ISO 8601 year (four digits) */
371 case 'g': /* ISO 8601 year (two digits) */
372 /*
373 ** From Arnold Robbins' strftime version 3.0: "the week number of the
374 ** year (the first Monday as the first day of week 1) as a decimal number
375 ** (01-53)."
376 ** (ado, 1993-05-24)
377 **
378 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
379 ** "Week 01 of a year is per definition the first week which has the
380 ** Thursday in this year, which is equivalent to the week which contains
381 ** the fourth day of January. In other words, the first week of a new year
382 ** is the week which has the majority of its days in the new year. Week 01
383 ** might also contain days from the previous year and the week before week
384 ** 01 of a year is the last week (52 or 53) of the previous year even if
385 ** it contains days from the new year. A week starts with Monday (day 1)
386 ** and ends with Sunday (day 7). For example, the first week of the year
387 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
388 ** (ado, 1996-01-02)
389 */
390 {
391 int year;
392 int base;
393 int yday;
394 int wday;
395 int w;
396
397 year = t->tm_year;
398 base = TM_YEAR_BASE;
399 yday = t->tm_yday;
400 wday = t->tm_wday;
401 for ( ; ; ) {
402 int len;
403 int bot;
404 int top;
405
406 len = isleap_sum(year, base) ?
407 DAYSPERLYEAR :
408 DAYSPERNYEAR;
409 /*
410 ** What yday (-3 ... 3) does
411 ** the ISO year begin on?
412 */
413 bot = ((yday + 11 - wday) %
414 DAYSPERWEEK) - 3;
415 /*
416 ** What yday does the NEXT
417 ** ISO year begin on?
418 */
419 top = bot -
420 (len % DAYSPERWEEK);
421 if (top < -3)
422 top += DAYSPERWEEK;
423 top += len;
424 if (yday >= top) {
425 ++base;
426 w = 1;
427 break;
428 }
429 if (yday >= bot) {
430 w = 1 + ((yday - bot) /
431 DAYSPERWEEK);
432 break;
433 }
434 --base;
435 yday += isleap_sum(year, base) ?
436 DAYSPERLYEAR :
437 DAYSPERNYEAR;
438 }
439 #ifdef XPG4_1994_04_09
440 if ((w == 52 &&
441 t->tm_mon == TM_JANUARY) ||
442 (w == 1 &&
443 t->tm_mon == TM_DECEMBER))
444 w = 53;
445 #endif /* defined XPG4_1994_04_09 */
446 if (*format == 'V')
447 pt = _conv(w, "%02d",
448 pt, ptlim);
449 else if (*format == 'g') {
450 *warnp = IN_ALL;
451 pt = _yconv(year, base,
452 false, true,
453 pt, ptlim);
454 } else pt = _yconv(year, base,
455 true, true,
456 pt, ptlim);
457 }
458 continue;
459 case 'v':
460 /*
461 ** From Arnold Robbins' strftime version 3.0:
462 ** "date as dd-bbb-YYYY"
463 ** (ado, 1993-05-24)
464 */
465 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
466 continue;
467 case 'W':
468 pt = _conv((t->tm_yday + DAYSPERWEEK -
469 (t->tm_wday ?
470 (t->tm_wday - 1) :
471 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
472 "%02d", pt, ptlim);
473 continue;
474 case 'w':
475 pt = _conv(t->tm_wday, "%d", pt, ptlim);
476 continue;
477 case 'X':
478 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
479 continue;
480 case 'x':
481 {
482 enum warn warn2 = IN_SOME;
483
484 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
485 if (warn2 == IN_ALL)
486 warn2 = IN_THIS;
487 if (warn2 > *warnp)
488 *warnp = warn2;
489 }
490 continue;
491 case 'y':
492 *warnp = IN_ALL;
493 pt = _yconv(t->tm_year, TM_YEAR_BASE,
494 false, true,
495 pt, ptlim);
496 continue;
497 case 'Y':
498 pt = _yconv(t->tm_year, TM_YEAR_BASE,
499 true, true,
500 pt, ptlim);
501 continue;
502 case 'Z':
503 #ifdef TM_ZONE
504 pt = _add(t->TM_ZONE, pt, ptlim);
505 #elif HAVE_TZNAME
506 if (t->tm_isdst >= 0)
507 pt = _add(tzname[t->tm_isdst != 0],
508 pt, ptlim);
509 #endif
510 /*
511 ** C99 and later say that %Z must be
512 ** replaced by the empty string if the
513 ** time zone abbreviation is not
514 ** determinable.
515 */
516 continue;
517 case 'z':
518 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE
519 {
520 long diff;
521 char const * sign;
522 bool negative;
523
524 # ifdef TM_GMTOFF
525 diff = t->TM_GMTOFF;
526 # else
527 /*
528 ** C99 and later say that the UT offset must
529 ** be computed by looking only at
530 ** tm_isdst. This requirement is
531 ** incorrect, since it means the code
532 ** must rely on magic (in this case
533 ** altzone and timezone), and the
534 ** magic might not have the correct
535 ** offset. Doing things correctly is
536 ** tricky and requires disobeying the standard;
537 ** see GNU C strftime for details.
538 ** For now, punt and conform to the
539 ** standard, even though it's incorrect.
540 **
541 ** C99 and later say that %z must be replaced by
542 ** the empty string if the time zone is not
543 ** determinable, so output nothing if the
544 ** appropriate variables are not available.
545 */
546 if (t->tm_isdst < 0)
547 continue;
548 if (t->tm_isdst == 0)
549 # if USG_COMPAT
550 diff = -timezone;
551 # else
552 continue;
553 # endif
554 else
555 # if ALTZONE
556 diff = -altzone;
557 # else
558 continue;
559 # endif
560 # endif
561 negative = diff < 0;
562 if (diff == 0) {
563 # ifdef TM_ZONE
564 negative = t->TM_ZONE[0] == '-';
565 # else
566 negative = t->tm_isdst < 0;
567 # if HAVE_TZNAME
568 if (tzname[t->tm_isdst != 0][0] == '-')
569 negative = true;
570 # endif
571 # endif
572 }
573 if (negative) {
574 sign = "-";
575 diff = -diff;
576 } else sign = "+";
577 pt = _add(sign, pt, ptlim);
578 diff /= SECSPERMIN;
579 diff = (diff / MINSPERHOUR) * 100 +
580 (diff % MINSPERHOUR);
581 pt = _conv(diff, "%04d", pt, ptlim);
582 }
583 #endif
584 continue;
585 case '+':
586 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
587 warnp);
588 continue;
589 case '%':
590 /*
591 ** X311J/88-090 (4.12.3.5): if conversion char is
592 ** undefined, behavior is undefined. Print out the
593 ** character itself as printf(3) also does.
594 */
595 default:
596 break;
597 }
598 }
599 if (pt == ptlim)
600 break;
601 *pt++ = *format;
602 }
603 return pt;
604 }
605
606 static char *
607 _conv(int n, const char *format, char *pt, const char *ptlim)
608 {
609 char buf[INT_STRLEN_MAXIMUM(int) + 1];
610
611 sprintf(buf, format, n);
612 return _add(buf, pt, ptlim);
613 }
614
615 static char *
616 _add(const char *str, char *pt, const char *ptlim)
617 {
618 while (pt < ptlim && (*pt = *str++) != '\0')
619 ++pt;
620 return pt;
621 }
622
623 /*
624 ** POSIX and the C Standard are unclear or inconsistent about
625 ** what %C and %y do if the year is negative or exceeds 9999.
626 ** Use the convention that %C concatenated with %y yields the
627 ** same output as %Y, and that %Y contains at least 4 bytes,
628 ** with more only if necessary.
629 */
630
631 static char *
632 _yconv(int a, int b, bool convert_top, bool convert_yy,
633 char *pt, const char *ptlim)
634 {
635 register int lead;
636 register int trail;
637
638 int DIVISOR = 100;
639 trail = a % DIVISOR + b % DIVISOR;
640 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
641 trail %= DIVISOR;
642 if (trail < 0 && lead > 0) {
643 trail += DIVISOR;
644 --lead;
645 } else if (lead < 0 && trail > 0) {
646 trail -= DIVISOR;
647 ++lead;
648 }
649 if (convert_top) {
650 if (lead == 0 && trail < 0)
651 pt = _add("-0", pt, ptlim);
652 else pt = _conv(lead, "%02d", pt, ptlim);
653 }
654 if (convert_yy)
655 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
656 return pt;
657 }