11590Srgrimes/* Convert a broken-down timestamp to a string.  */
21590Srgrimes
31590Srgrimes/* Copyright 1989 The Regents of the University of California.
41590Srgrimes   All rights reserved.
51590Srgrimes
61590Srgrimes   Redistribution and use in source and binary forms, with or without
71590Srgrimes   modification, are permitted provided that the following conditions
81590Srgrimes   are met:
91590Srgrimes   1. Redistributions of source code must retain the above copyright
101590Srgrimes      notice, this list of conditions and the following disclaimer.
111590Srgrimes   2. Redistributions in binary form must reproduce the above copyright
121590Srgrimes      notice, this list of conditions and the following disclaimer in the
131590Srgrimes      documentation and/or other materials provided with the distribution.
141590Srgrimes   3. Neither the name of the University nor the names of its contributors
151590Srgrimes      may be used to endorse or promote products derived from this software
161590Srgrimes      without specific prior written permission.
171590Srgrimes
181590Srgrimes   THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
191590Srgrimes   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
201590Srgrimes   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
211590Srgrimes   ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
221590Srgrimes   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
231590Srgrimes   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
241590Srgrimes   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
251590Srgrimes   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
261590Srgrimes   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
271590Srgrimes   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
281590Srgrimes   SUCH DAMAGE.  */
291590Srgrimes
301590Srgrimes/*
311590Srgrimes** Based on the UCB version with the copyright notice appearing above.
321590Srgrimes**
3387712Smarkm** This is ANSIish only when "multibyte character == plain character".
3487712Smarkm*/
3587712Smarkm
3687712Smarkm#include "private.h"
371590Srgrimes
3887712Smarkm#include <fcntl.h>
3969528Sasmodai#include <locale.h>
401590Srgrimes#include <stdio.h>
411590Srgrimes
421590Srgrimes#ifndef DEPRECATE_TWO_DIGIT_YEARS
4374876Sdwmalone# define DEPRECATE_TWO_DIGIT_YEARS false
4487712Smarkm#endif
4587712Smarkm
461590Srgrimesstruct lc_time_T {
471590Srgrimes	const char *	mon[MONSPERYEAR];
481590Srgrimes	const char *	month[MONSPERYEAR];
491590Srgrimes	const char *	wday[DAYSPERWEEK];
5087712Smarkm	const char *	weekday[DAYSPERWEEK];
5187712Smarkm	const char *	X_fmt;
521590Srgrimes	const char *	x_fmt;
531590Srgrimes	const char *	c_fmt;
541590Srgrimes	const char *	am;
55193488Sbrian	const char *	pm;
561590Srgrimes	const char *	date_fmt;
5717833Sadam};
5817833Sadam
591590Srgrimesstatic const struct lc_time_T	C_time_locale = {
601590Srgrimes	{
611590Srgrimes		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
62201382Sed		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
631590Srgrimes	}, {
6417825Speter		"January", "February", "March", "April", "May", "June",
651590Srgrimes		"July", "August", "September", "October", "November", "December"
6674876Sdwmalone	}, {
6774876Sdwmalone		"Sun", "Mon", "Tue", "Wed",
6874876Sdwmalone		"Thu", "Fri", "Sat"
6974876Sdwmalone	}, {
7074876Sdwmalone		"Sunday", "Monday", "Tuesday", "Wednesday",
7174876Sdwmalone		"Thursday", "Friday", "Saturday"
72137157Spaul	},
7374876Sdwmalone
7474876Sdwmalone	/* X_fmt */
7574876Sdwmalone	"%H:%M:%S",
7674876Sdwmalone
7774876Sdwmalone	/*
78139994Sdwmalone	** x_fmt
7974876Sdwmalone	** C99 and later require this format.
8074876Sdwmalone	** Using just numbers (as here) makes Quakers happier;
8174876Sdwmalone	** it's also compatible with SVR4.
8274876Sdwmalone	*/
8374876Sdwmalone	"%m/%d/%y",
8474876Sdwmalone
8574876Sdwmalone	/*
8674876Sdwmalone	** c_fmt
8774876Sdwmalone	** C99 and later require this format.
8874876Sdwmalone	** Previously this code used "%D %X", but we now conform to C99.
8974876Sdwmalone	** Note that
9074876Sdwmalone	**	"%a %b %d %H:%M:%S %Y"
9174876Sdwmalone	** is used by Solaris 2.3.
9274876Sdwmalone	*/
9374876Sdwmalone	"%a %b %e %T %Y",
9474876Sdwmalone
9574876Sdwmalone	/* am */
9674876Sdwmalone	"AM",
9774876Sdwmalone
98137157Spaul	/* pm */
9974876Sdwmalone	"PM",
10074876Sdwmalone
10174876Sdwmalone	/* date_fmt */
10274876Sdwmalone	"%a %b %e %H:%M:%S %Z %Y"
10374876Sdwmalone};
10474876Sdwmalone
10574876Sdwmaloneenum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
106139994Sdwmalone
10774876Sdwmalonestatic char *	_add(const char *, char *, const char *);
10874876Sdwmalonestatic char *	_conv(int, const char *, char *, const char *);
10974876Sdwmalonestatic char *	_fmt(const char *, const struct tm *, char *, const char *,
11074876Sdwmalone		     enum warn *);
11174876Sdwmalonestatic char *	_yconv(int, int, bool, bool, char *, char const *);
11274876Sdwmalone
11374876Sdwmalone#ifndef YEAR_2000_NAME
11474876Sdwmalone# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
11574876Sdwmalone#endif /* !defined YEAR_2000_NAME */
116251565Sjh
117251565Sjh#if HAVE_STRFTIME_L
118251565Sjhsize_t
119251565Sjhstrftime_l(char *restrict s, size_t maxsize, char const *restrict format,
120251565Sjh	   struct tm const *restrict t,
121251565Sjh	   ATTRIBUTE_MAYBE_UNUSED locale_t locale)
122251565Sjh{
123251565Sjh  /* Just call strftime, as only the C locale is supported.  */
124251565Sjh  return strftime(s, maxsize, format, t);
125251565Sjh}
126251565Sjh#endif
127251565Sjh
128251565Sjhsize_t
129251565Sjhstrftime(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