1/*
2 * Copyright (c) 1989 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Copyright (c) 2011 The FreeBSD Foundation
6 * All rights reserved.
7 * Portions of this software were developed by David Chisnall
8 * under sponsorship from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms are permitted
11 * provided that the above copyright notice and this paragraph are
12 * duplicated in all such forms and that any documentation,
13 * advertising materials, and other materials related to such
14 * distribution and use acknowledge that the software was developed
15 * by the University of California, Berkeley. The name of the
16 * University may not be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 */
22
23#ifndef lint
24#ifndef NOID
25static const char	elsieid[] = "@(#)strftime.3	8.3";
26/*
27** Based on the UCB version with the ID appearing below.
28** This is ANSIish only when "multibyte character == plain character".
29*/
30#endif /* !defined NOID */
31#endif /* !defined lint */
32
33#include "namespace.h"
34#include "private.h"
35
36#if defined(LIBC_SCCS) && !defined(lint)
37static const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
38#endif /* LIBC_SCCS and not lint */
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD$");
41
42#include "tzfile.h"
43#include <fcntl.h>
44#include <sys/stat.h>
45#include "un-namespace.h"
46#include "timelocal.h"
47
48static char *	_add(const char *, char *, const char *);
49static char *	_conv(int, const char *, char *, const char *);
50static char *	_fmt(const char *, const struct tm *, char *, const char *,
51			int *, locale_t);
52static char *	_yconv(int, int, int, int, char *, const char *);
53
54extern char *	tzname[];
55
56#ifndef YEAR_2000_NAME
57#define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
58#endif /* !defined YEAR_2000_NAME */
59
60#define IN_NONE	0
61#define IN_SOME	1
62#define IN_THIS	2
63#define IN_ALL	3
64
65#define PAD_DEFAULT	0
66#define PAD_LESS	1
67#define PAD_SPACE	2
68#define PAD_ZERO	3
69
70static const char fmt_padding[][4][5] = {
71	/* DEFAULT,	LESS,	SPACE,	ZERO */
72#define PAD_FMT_MONTHDAY	0
73#define PAD_FMT_HMS		0
74#define PAD_FMT_CENTURY		0
75#define PAD_FMT_SHORTYEAR	0
76#define PAD_FMT_MONTH		0
77#define PAD_FMT_WEEKOFYEAR	0
78#define PAD_FMT_DAYOFMONTH	0
79	{ "%02d",	"%d",	"%2d",	"%02d" },
80#define PAD_FMT_SDAYOFMONTH	1
81#define PAD_FMT_SHMS		1
82	{ "%2d",	"%d",	"%2d",	"%02d" },
83#define	PAD_FMT_DAYOFYEAR	2
84	{ "%03d",	"%d",	"%3d",	"%03d" },
85#define PAD_FMT_YEAR		3
86	{ "%04d",	"%d",	"%4d",	"%04d" }
87};
88
89size_t
90strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format,
91    const struct tm * __restrict t, locale_t loc)
92{
93	char *	p;
94	int	warn;
95	FIX_LOCALE(loc);
96
97	tzset();
98	warn = IN_NONE;
99	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn, loc);
100#ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
101	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
102		(void) fprintf_l(stderr, loc, "\n");
103		if (format == NULL)
104			(void) fprintf_l(stderr, loc, "NULL strftime format ");
105		else	(void) fprintf_l(stderr, loc, "strftime format \"%s\" ",
106				format);
107		(void) fprintf_l(stderr, loc, "yields only two digits of years in ");
108		if (warn == IN_SOME)
109			(void) fprintf_l(stderr, loc, "some locales");
110		else if (warn == IN_THIS)
111			(void) fprintf_l(stderr, loc, "the current locale");
112		else	(void) fprintf_l(stderr, loc, "all locales");
113		(void) fprintf_l(stderr, loc, "\n");
114	}
115#endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
116	if (p == s + maxsize)
117		return 0;
118	*p = '\0';
119	return p - s;
120}
121
122size_t
123strftime(char * __restrict s, size_t maxsize, const char * __restrict format,
124    const struct tm * __restrict t)
125{
126	return strftime_l(s, maxsize, format, t, __get_locale());
127}
128
129static char *
130_fmt(format, t, pt, ptlim, warnp, loc)
131const char *		format;
132const struct tm * const	t;
133char *			pt;
134const char * const	ptlim;
135int *			warnp;
136locale_t	loc;
137{
138	int Ealternative, Oalternative, PadIndex;
139	struct lc_time_T *tptr = __get_current_time_locale(loc);
140
141	for ( ; *format; ++format) {
142		if (*format == '%') {
143			Ealternative = 0;
144			Oalternative = 0;
145			PadIndex	 = PAD_DEFAULT;
146label:
147			switch (*++format) {
148			case '\0':
149				--format;
150				break;
151			case 'A':
152				pt = _add((t->tm_wday < 0 ||
153					t->tm_wday >= DAYSPERWEEK) ?
154					"?" : tptr->weekday[t->tm_wday],
155					pt, ptlim);
156				continue;
157			case 'a':
158				pt = _add((t->tm_wday < 0 ||
159					t->tm_wday >= DAYSPERWEEK) ?
160					"?" : tptr->wday[t->tm_wday],
161					pt, ptlim);
162				continue;
163			case 'B':
164				pt = _add((t->tm_mon < 0 ||
165					t->tm_mon >= MONSPERYEAR) ?
166					"?" : (Oalternative ? tptr->alt_month :
167					tptr->month)[t->tm_mon],
168					pt, ptlim);
169				continue;
170			case 'b':
171			case 'h':
172				pt = _add((t->tm_mon < 0 ||
173					t->tm_mon >= MONSPERYEAR) ?
174					"?" : tptr->mon[t->tm_mon],
175					pt, ptlim);
176				continue;
177			case 'C':
178				/*
179				** %C used to do a...
180				**	_fmt("%a %b %e %X %Y", t);
181				** ...whereas now POSIX 1003.2 calls for
182				** something completely different.
183				** (ado, 1993-05-24)
184				*/
185				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
186					pt, ptlim);
187				continue;
188			case 'c':
189				{
190				int warn2 = IN_SOME;
191
192				pt = _fmt(tptr->c_fmt, t, pt, ptlim, &warn2, loc);
193				if (warn2 == IN_ALL)
194					warn2 = IN_THIS;
195				if (warn2 > *warnp)
196					*warnp = warn2;
197				}
198				continue;
199			case 'D':
200				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, loc);
201				continue;
202			case 'd':
203				pt = _conv(t->tm_mday, fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
204					pt, ptlim);
205				continue;
206			case 'E':
207				if (Ealternative || Oalternative)
208					break;
209				Ealternative++;
210				goto label;
211			case 'O':
212				/*
213				** C99 locale modifiers.
214				** The sequences
215				**	%Ec %EC %Ex %EX %Ey %EY
216				**	%Od %oe %OH %OI %Om %OM
217				**	%OS %Ou %OU %OV %Ow %OW %Oy
218				** are supposed to provide alternate
219				** representations.
220				**
221				** FreeBSD extension
222				**      %OB
223				*/
224				if (Ealternative || Oalternative)
225					break;
226				Oalternative++;
227				goto label;
228			case 'e':
229				pt = _conv(t->tm_mday,
230					fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex], pt, ptlim);
231				continue;
232			case 'F':
233				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, loc);
234				continue;
235			case 'H':
236				pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_HMS][PadIndex],
237					pt, ptlim);
238				continue;
239			case 'I':
240				pt = _conv((t->tm_hour % 12) ?
241					(t->tm_hour % 12) : 12,
242					fmt_padding[PAD_FMT_HMS][PadIndex], pt, ptlim);
243				continue;
244			case 'j':
245				pt = _conv(t->tm_yday + 1,
246					fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex], pt, ptlim);
247				continue;
248			case 'k':
249				/*
250				** This used to be...
251				**	_conv(t->tm_hour % 12 ?
252				**		t->tm_hour % 12 : 12, 2, ' ');
253				** ...and has been changed to the below to
254				** match SunOS 4.1.1 and Arnold Robbins'
255				** strftime version 3.0. That is, "%k" and
256				** "%l" have been swapped.
257				** (ado, 1993-05-24)
258				*/
259				pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_SHMS][PadIndex],
260					pt, ptlim);
261				continue;
262#ifdef KITCHEN_SINK
263			case 'K':
264				/*
265				** After all this time, still unclaimed!
266				*/
267				pt = _add("kitchen sink", pt, ptlim);
268				continue;
269#endif /* defined KITCHEN_SINK */
270			case 'l':
271				/*
272				** This used to be...
273				**	_conv(t->tm_hour, 2, ' ');
274				** ...and has been changed to the below to
275				** match SunOS 4.1.1 and Arnold Robbin's
276				** strftime version 3.0. That is, "%k" and
277				** "%l" have been swapped.
278				** (ado, 1993-05-24)
279				*/
280				pt = _conv((t->tm_hour % 12) ?
281					(t->tm_hour % 12) : 12,
282					fmt_padding[PAD_FMT_SHMS][PadIndex], pt, ptlim);
283				continue;
284			case 'M':
285				pt = _conv(t->tm_min, fmt_padding[PAD_FMT_HMS][PadIndex],
286					pt, ptlim);
287				continue;
288			case 'm':
289				pt = _conv(t->tm_mon + 1,
290					fmt_padding[PAD_FMT_MONTH][PadIndex], pt, ptlim);
291				continue;
292			case 'n':
293				pt = _add("\n", pt, ptlim);
294				continue;
295			case 'p':
296				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
297					tptr->pm :
298					tptr->am,
299					pt, ptlim);
300				continue;
301			case 'R':
302				pt = _fmt("%H:%M", t, pt, ptlim, warnp, loc);
303				continue;
304			case 'r':
305				pt = _fmt(tptr->ampm_fmt, t, pt, ptlim,
306					warnp, loc);
307				continue;
308			case 'S':
309				pt = _conv(t->tm_sec, fmt_padding[PAD_FMT_HMS][PadIndex],
310					pt, ptlim);
311				continue;
312			case 's':
313				{
314					struct tm	tm;
315					char		buf[INT_STRLEN_MAXIMUM(
316								time_t) + 1];
317					time_t		mkt;
318
319					tm = *t;
320					mkt = mktime(&tm);
321					if (TYPE_SIGNED(time_t))
322						(void) sprintf(buf, "%ld",
323							(long) mkt);
324					else	(void) sprintf(buf, "%lu",
325							(unsigned long) mkt);
326					pt = _add(buf, pt, ptlim);
327				}
328				continue;
329			case 'T':
330				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, loc);
331				continue;
332			case 't':
333				pt = _add("\t", pt, ptlim);
334				continue;
335			case 'U':
336				pt = _conv((t->tm_yday + DAYSPERWEEK -
337					t->tm_wday) / DAYSPERWEEK,
338					fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], pt, ptlim);
339				continue;
340			case 'u':
341				/*
342				** From Arnold Robbins' strftime version 3.0:
343				** "ISO 8601: Weekday as a decimal number
344				** [1 (Monday) - 7]"
345				** (ado, 1993-05-24)
346				*/
347				pt = _conv((t->tm_wday == 0) ?
348					DAYSPERWEEK : t->tm_wday,
349					"%d", pt, ptlim);
350				continue;
351			case 'V':	/* ISO 8601 week number */
352			case 'G':	/* ISO 8601 year (four digits) */
353			case 'g':	/* ISO 8601 year (two digits) */
354/*
355** From Arnold Robbins' strftime version 3.0: "the week number of the
356** year (the first Monday as the first day of week 1) as a decimal number
357** (01-53)."
358** (ado, 1993-05-24)
359**
360** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
361** "Week 01 of a year is per definition the first week which has the
362** Thursday in this year, which is equivalent to the week which contains
363** the fourth day of January. In other words, the first week of a new year
364** is the week which has the majority of its days in the new year. Week 01
365** might also contain days from the previous year and the week before week
366** 01 of a year is the last week (52 or 53) of the previous year even if
367** it contains days from the new year. A week starts with Monday (day 1)
368** and ends with Sunday (day 7). For example, the first week of the year
369** 1997 lasts from 1996-12-30 to 1997-01-05..."
370** (ado, 1996-01-02)
371*/
372				{
373					int	year;
374					int	base;
375					int	yday;
376					int	wday;
377					int	w;
378
379					year = t->tm_year;
380					base = TM_YEAR_BASE;
381					yday = t->tm_yday;
382					wday = t->tm_wday;
383					for ( ; ; ) {
384						int	len;
385						int	bot;
386						int	top;
387
388						len = isleap_sum(year, base) ?
389							DAYSPERLYEAR :
390							DAYSPERNYEAR;
391						/*
392						** What yday (-3 ... 3) does
393						** the ISO year begin on?
394						*/
395						bot = ((yday + 11 - wday) %
396							DAYSPERWEEK) - 3;
397						/*
398						** What yday does the NEXT
399						** ISO year begin on?
400						*/
401						top = bot -
402							(len % DAYSPERWEEK);
403						if (top < -3)
404							top += DAYSPERWEEK;
405						top += len;
406						if (yday >= top) {
407							++base;
408							w = 1;
409							break;
410						}
411						if (yday >= bot) {
412							w = 1 + ((yday - bot) /
413								DAYSPERWEEK);
414							break;
415						}
416						--base;
417						yday += isleap_sum(year, base) ?
418							DAYSPERLYEAR :
419							DAYSPERNYEAR;
420					}
421#ifdef XPG4_1994_04_09
422					if ((w == 52 &&
423						t->tm_mon == TM_JANUARY) ||
424						(w == 1 &&
425						t->tm_mon == TM_DECEMBER))
426							w = 53;
427#endif /* defined XPG4_1994_04_09 */
428					if (*format == 'V')
429						pt = _conv(w, fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
430							pt, ptlim);
431					else if (*format == 'g') {
432						*warnp = IN_ALL;
433						pt = _yconv(year, base, 0, 1,
434							pt, ptlim);
435					} else	pt = _yconv(year, base, 1, 1,
436							pt, ptlim);
437				}
438				continue;
439			case 'v':
440				/*
441				** From Arnold Robbins' strftime version 3.0:
442				** "date as dd-bbb-YYYY"
443				** (ado, 1993-05-24)
444				*/
445				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, loc);
446				continue;
447			case 'W':
448				pt = _conv((t->tm_yday + DAYSPERWEEK -
449					(t->tm_wday ?
450					(t->tm_wday - 1) :
451					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
452					fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], pt, ptlim);
453				continue;
454			case 'w':
455				pt = _conv(t->tm_wday, "%d", pt, ptlim);
456				continue;
457			case 'X':
458				pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp, loc);
459				continue;
460			case 'x':
461				{
462				int	warn2 = IN_SOME;
463
464				pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2, loc);
465				if (warn2 == IN_ALL)
466					warn2 = IN_THIS;
467				if (warn2 > *warnp)
468					*warnp = warn2;
469				}
470				continue;
471			case 'y':
472				*warnp = IN_ALL;
473				pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
474					pt, ptlim);
475				continue;
476			case 'Y':
477				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
478					pt, ptlim);
479				continue;
480			case 'Z':
481#ifdef TM_ZONE
482				if (t->TM_ZONE != NULL)
483					pt = _add(t->TM_ZONE, pt, ptlim);
484				else
485#endif /* defined TM_ZONE */
486				if (t->tm_isdst >= 0)
487					pt = _add(tzname[t->tm_isdst != 0],
488						pt, ptlim);
489				/*
490				** C99 says that %Z must be replaced by the
491				** empty string if the time zone is not
492				** determinable.
493				*/
494				continue;
495			case 'z':
496				{
497				int		diff;
498				char const *	sign;
499
500				if (t->tm_isdst < 0)
501					continue;
502#ifdef TM_GMTOFF
503				diff = t->TM_GMTOFF;
504#else /* !defined TM_GMTOFF */
505				/*
506				** C99 says that the UTC offset must
507				** be computed by looking only at
508				** tm_isdst. This requirement is
509				** incorrect, since it means the code
510				** must rely on magic (in this case
511				** altzone and timezone), and the
512				** magic might not have the correct
513				** offset. Doing things correctly is
514				** tricky and requires disobeying C99;
515				** see GNU C strftime for details.
516				** For now, punt and conform to the
517				** standard, even though it's incorrect.
518				**
519				** C99 says that %z must be replaced by the
520				** empty string if the time zone is not
521				** determinable, so output nothing if the
522				** appropriate variables are not available.
523				*/
524				if (t->tm_isdst == 0)
525#ifdef USG_COMPAT
526					diff = -timezone;
527#else /* !defined USG_COMPAT */
528					continue;
529#endif /* !defined USG_COMPAT */
530				else
531#ifdef ALTZONE
532					diff = -altzone;
533#else /* !defined ALTZONE */
534					continue;
535#endif /* !defined ALTZONE */
536#endif /* !defined TM_GMTOFF */
537				if (diff < 0) {
538					sign = "-";
539					diff = -diff;
540				} else	sign = "+";
541				pt = _add(sign, pt, ptlim);
542				diff /= SECSPERMIN;
543				diff = (diff / MINSPERHOUR) * 100 +
544					(diff % MINSPERHOUR);
545				pt = _conv(diff,
546					fmt_padding[PAD_FMT_YEAR][PadIndex], pt, ptlim);
547				}
548				continue;
549			case '+':
550				pt = _fmt(tptr->date_fmt, t, pt, ptlim,
551					warnp, loc);
552				continue;
553			case '-':
554				if (PadIndex != PAD_DEFAULT)
555					break;
556				PadIndex = PAD_LESS;
557				goto label;
558			case '_':
559				if (PadIndex != PAD_DEFAULT)
560					break;
561				PadIndex = PAD_SPACE;
562				goto label;
563			case '0':
564				if (PadIndex != PAD_DEFAULT)
565					break;
566				PadIndex = PAD_ZERO;
567				goto label;
568			case '%':
569			/*
570			** X311J/88-090 (4.12.3.5): if conversion char is
571			** undefined, behavior is undefined. Print out the
572			** character itself as printf(3) also does.
573			*/
574			default:
575				break;
576			}
577		}
578		if (pt == ptlim)
579			break;
580		*pt++ = *format;
581	}
582	return pt;
583}
584
585static char *
586_conv(n, format, pt, ptlim)
587const int		n;
588const char * const	format;
589char * const		pt;
590const char * const	ptlim;
591{
592	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
593
594	(void) sprintf(buf, format, n);
595	return _add(buf, pt, ptlim);
596}
597
598static char *
599_add(str, pt, ptlim)
600const char *		str;
601char *			pt;
602const char * const	ptlim;
603{
604	while (pt < ptlim && (*pt = *str++) != '\0')
605		++pt;
606	return pt;
607}
608
609/*
610** POSIX and the C Standard are unclear or inconsistent about
611** what %C and %y do if the year is negative or exceeds 9999.
612** Use the convention that %C concatenated with %y yields the
613** same output as %Y, and that %Y contains at least 4 bytes,
614** with more only if necessary.
615*/
616
617static char *
618_yconv(a, b, convert_top, convert_yy, pt, ptlim)
619const int		a;
620const int		b;
621const int		convert_top;
622const int		convert_yy;
623char *			pt;
624const char * const	ptlim;
625{
626	register int	lead;
627	register int	trail;
628
629#define DIVISOR	100
630	trail = a % DIVISOR + b % DIVISOR;
631	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
632	trail %= DIVISOR;
633	if (trail < 0 && lead > 0) {
634		trail += DIVISOR;
635		--lead;
636	} else if (lead < 0 && trail > 0) {
637		trail -= DIVISOR;
638		++lead;
639	}
640	if (convert_top) {
641		if (lead == 0 && trail < 0)
642			pt = _add("-0", pt, ptlim);
643		else	pt = _conv(lead, "%02d", pt, ptlim);
644	}
645	if (convert_yy)
646		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
647	return pt;
648}
649