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
46struct 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
59static 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
105enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
106
107static char *	_add(const char *, char *, const char *);
108static char *	_conv(int, const char *, char *, const char *);
109static char *	_fmt(const char *, const struct tm *, char *, const char *,
110		     enum warn *);
111static 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
118size_t
119strftime_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
128size_t
129strftime(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
163static 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 == '%') {
171label:
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
609static 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
618static 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
634static 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