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