xref: /freebsd/contrib/tzcode/strftime.c (revision e1c4c8dd8d2d10b6104f06856a77bd5b4813a801)
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 *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
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 *
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 *
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 *
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 *
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