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 *, locale_t);
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 *, locale_t);
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) fputs("NULL strftime format ", stderr);
105		else	(void) fprintf_l(stderr, loc, "strftime format \"%s\" ",
106				format);
107		(void) fputs("yields only two digits of years in ", stderr);
108		if (warn == IN_SOME)
109			(void) fputs("some locales", stderr);
110		else if (warn == IN_THIS)
111			(void) fputs("the current locale", stderr);
112		else	(void) fputs("all locales", stderr);
113		(void) fputs("\n", stderr);
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(const char *format, const struct tm * const t, char *pt,
131    const char * const ptlim, int *warnp, locale_t loc)
132{
133	int Ealternative, Oalternative, PadIndex;
134	struct lc_time_T *tptr = __get_current_time_locale(loc);
135
136	for ( ; *format; ++format) {
137		if (*format == '%') {
138			Ealternative = 0;
139			Oalternative = 0;
140			PadIndex	 = PAD_DEFAULT;
141label:
142			switch (*++format) {
143			case '\0':
144				--format;
145				break;
146			case 'A':
147				pt = _add((t->tm_wday < 0 ||
148					t->tm_wday >= DAYSPERWEEK) ?
149					"?" : tptr->weekday[t->tm_wday],
150					pt, ptlim);
151				continue;
152			case 'a':
153				pt = _add((t->tm_wday < 0 ||
154					t->tm_wday >= DAYSPERWEEK) ?
155					"?" : tptr->wday[t->tm_wday],
156					pt, ptlim);
157				continue;
158			case 'B':
159				pt = _add((t->tm_mon < 0 ||
160					t->tm_mon >= MONSPERYEAR) ?
161					"?" : (Oalternative ? tptr->alt_month :
162					tptr->month)[t->tm_mon],
163					pt, ptlim);
164				continue;
165			case 'b':
166			case 'h':
167				pt = _add((t->tm_mon < 0 ||
168					t->tm_mon >= MONSPERYEAR) ?
169					"?" : tptr->mon[t->tm_mon],
170					pt, ptlim);
171				continue;
172			case 'C':
173				/*
174				 * %C used to do a...
175				 *	_fmt("%a %b %e %X %Y", t);
176				 * ...whereas now POSIX 1003.2 calls for
177				 * something completely different.
178				 * (ado, 1993-05-24)
179				 */
180				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
181					pt, ptlim, loc);
182				continue;
183			case 'c':
184				{
185				int warn2 = IN_SOME;
186
187				pt = _fmt(tptr->c_fmt, t, pt, ptlim, &warn2, loc);
188				if (warn2 == IN_ALL)
189					warn2 = IN_THIS;
190				if (warn2 > *warnp)
191					*warnp = warn2;
192				}
193				continue;
194			case 'D':
195				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, loc);
196				continue;
197			case 'd':
198				pt = _conv(t->tm_mday,
199					fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex],
200					pt, ptlim, loc);
201				continue;
202			case 'E':
203				if (Ealternative || Oalternative)
204					break;
205				Ealternative++;
206				goto label;
207			case 'O':
208				/*
209				 * C99 locale modifiers.
210				 * The sequences
211				 *	%Ec %EC %Ex %EX %Ey %EY
212				 *	%Od %oe %OH %OI %Om %OM
213				 *	%OS %Ou %OU %OV %Ow %OW %Oy
214				 * are supposed to provide alternate
215				 * representations.
216				 *
217				 * FreeBSD extension
218				 *      %OB
219				 */
220				if (Ealternative || Oalternative)
221					break;
222				Oalternative++;
223				goto label;
224			case 'e':
225				pt = _conv(t->tm_mday,
226					fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex],
227					pt, ptlim, loc);
228				continue;
229			case 'F':
230				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, loc);
231				continue;
232			case 'H':
233				pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_HMS][PadIndex],
234					pt, ptlim, loc);
235				continue;
236			case 'I':
237				pt = _conv((t->tm_hour % 12) ?
238					(t->tm_hour % 12) : 12,
239					fmt_padding[PAD_FMT_HMS][PadIndex],
240					pt, ptlim, loc);
241				continue;
242			case 'j':
243				pt = _conv(t->tm_yday + 1,
244					fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex],
245					pt, ptlim, loc);
246				continue;
247			case 'k':
248				/*
249				 * This used to be...
250				 *	_conv(t->tm_hour % 12 ?
251				 *		t->tm_hour % 12 : 12, 2, ' ');
252				 * ...and has been changed to the below to
253				 * match SunOS 4.1.1 and Arnold Robbins'
254				 * strftime version 3.0. That is, "%k" and
255				 * "%l" have been swapped.
256				 * (ado, 1993-05-24)
257				 */
258				pt = _conv(t->tm_hour, fmt_padding[PAD_FMT_SHMS][PadIndex],
259					pt, ptlim, loc);
260				continue;
261#ifdef KITCHEN_SINK
262			case 'K':
263				/*
264				** After all this time, still unclaimed!
265				*/
266				pt = _add("kitchen sink", pt, ptlim);
267				continue;
268#endif /* defined KITCHEN_SINK */
269			case 'l':
270				/*
271				 * This used to be...
272				 *	_conv(t->tm_hour, 2, ' ');
273				 * ...and has been changed to the below to
274				 * match SunOS 4.1.1 and Arnold Robbin's
275				 * strftime version 3.0. That is, "%k" and
276				 * "%l" have been swapped.
277				 * (ado, 1993-05-24)
278				 */
279				pt = _conv((t->tm_hour % 12) ?
280					(t->tm_hour % 12) : 12,
281					fmt_padding[PAD_FMT_SHMS][PadIndex],
282					pt, ptlim, loc);
283				continue;
284			case 'M':
285				pt = _conv(t->tm_min, fmt_padding[PAD_FMT_HMS][PadIndex],
286					pt, ptlim, loc);
287				continue;
288			case 'm':
289				pt = _conv(t->tm_mon + 1,
290					fmt_padding[PAD_FMT_MONTH][PadIndex],
291					pt, ptlim, loc);
292				continue;
293			case 'n':
294				pt = _add("\n", pt, ptlim);
295				continue;
296			case 'p':
297				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
298					tptr->pm : 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, loc);
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_l(buf, loc, "%ld",
323							(long) mkt);
324					else	(void) sprintf_l(buf, loc, "%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],
339					pt, ptlim, loc);
340				continue;
341			case 'u':
342				/*
343				 * From Arnold Robbins' strftime version 3.0:
344				 * "ISO 8601: Weekday as a decimal number
345				 * [1 (Monday) - 7]"
346				 * (ado, 1993-05-24)
347				 */
348				pt = _conv((t->tm_wday == 0) ?
349					DAYSPERWEEK : t->tm_wday,
350					"%d", pt, ptlim, loc);
351				continue;
352			case 'V':	/* ISO 8601 week number */
353			case 'G':	/* ISO 8601 year (four digits) */
354			case 'g':	/* ISO 8601 year (two digits) */
355/*
356 * From Arnold Robbins' strftime version 3.0: "the week number of the
357 * year (the first Monday as the first day of week 1) as a decimal number
358 * (01-53)."
359 * (ado, 1993-05-24)
360 *
361 * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
362 * "Week 01 of a year is per definition the first week which has the
363 * Thursday in this year, which is equivalent to the week which contains
364 * the fourth day of January. In other words, the first week of a new year
365 * is the week which has the majority of its days in the new year. Week 01
366 * might also contain days from the previous year and the week before week
367 * 01 of a year is the last week (52 or 53) of the previous year even if
368 * it contains days from the new year. A week starts with Monday (day 1)
369 * and ends with Sunday (day 7). For example, the first week of the year
370 * 1997 lasts from 1996-12-30 to 1997-01-05..."
371 * (ado, 1996-01-02)
372 */
373				{
374					int	year;
375					int	base;
376					int	yday;
377					int	wday;
378					int	w;
379
380					year = t->tm_year;
381					base = TM_YEAR_BASE;
382					yday = t->tm_yday;
383					wday = t->tm_wday;
384					for ( ; ; ) {
385						int	len;
386						int	bot;
387						int	top;
388
389						len = isleap_sum(year, base) ?
390							DAYSPERLYEAR :
391							DAYSPERNYEAR;
392						/*
393						 * What yday (-3 ... 3) does
394						 * the ISO year begin on?
395						 */
396						bot = ((yday + 11 - wday) %
397							DAYSPERWEEK) - 3;
398						/*
399						 * What yday does the NEXT
400						 * ISO year begin on?
401						 */
402						top = bot -
403							(len % DAYSPERWEEK);
404						if (top < -3)
405							top += DAYSPERWEEK;
406						top += len;
407						if (yday >= top) {
408							++base;
409							w = 1;
410							break;
411						}
412						if (yday >= bot) {
413							w = 1 + ((yday - bot) /
414								DAYSPERWEEK);
415							break;
416						}
417						--base;
418						yday += isleap_sum(year, base) ?
419							DAYSPERLYEAR :
420							DAYSPERNYEAR;
421					}
422#ifdef XPG4_1994_04_09
423					if ((w == 52 &&
424						t->tm_mon == TM_JANUARY) ||
425						(w == 1 &&
426						t->tm_mon == TM_DECEMBER))
427							w = 53;
428#endif /* defined XPG4_1994_04_09 */
429					if (*format == 'V')
430						pt = _conv(w, fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
431							pt, ptlim, loc);
432					else if (*format == 'g') {
433						*warnp = IN_ALL;
434						pt = _yconv(year, base, 0, 1,
435							pt, ptlim, loc);
436					} else	pt = _yconv(year, base, 1, 1,
437							pt, ptlim, loc);
438				}
439				continue;
440			case 'v':
441				/*
442				 * From Arnold Robbins' strftime version 3.0:
443				 * "date as dd-bbb-YYYY"
444				 * (ado, 1993-05-24)
445				 */
446				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, loc);
447				continue;
448			case 'W':
449				pt = _conv((t->tm_yday + DAYSPERWEEK -
450					(t->tm_wday ?
451					(t->tm_wday - 1) :
452					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
453					fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex],
454					pt, ptlim, loc);
455				continue;
456			case 'w':
457				pt = _conv(t->tm_wday, "%d", pt, ptlim, loc);
458				continue;
459			case 'X':
460				pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp, loc);
461				continue;
462			case 'x':
463				{
464				int	warn2 = IN_SOME;
465
466				pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2, loc);
467				if (warn2 == IN_ALL)
468					warn2 = IN_THIS;
469				if (warn2 > *warnp)
470					*warnp = warn2;
471				}
472				continue;
473			case 'y':
474				*warnp = IN_ALL;
475				pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
476					pt, ptlim, loc);
477				continue;
478			case 'Y':
479				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
480					pt, ptlim, loc);
481				continue;
482			case 'Z':
483#ifdef TM_ZONE
484				if (t->TM_ZONE != NULL)
485					pt = _add(t->TM_ZONE, pt, ptlim);
486				else
487#endif /* defined TM_ZONE */
488				if (t->tm_isdst >= 0)
489					pt = _add(tzname[t->tm_isdst != 0],
490						pt, ptlim);
491				/*
492				 * C99 says that %Z must be replaced by the
493				 * empty string if the time zone is not
494				 * determinable.
495				 */
496				continue;
497			case 'z':
498				{
499				int		diff;
500				char const *	sign;
501
502				if (t->tm_isdst < 0)
503					continue;
504#ifdef TM_GMTOFF
505				diff = t->TM_GMTOFF;
506#else /* !defined TM_GMTOFF */
507				/*
508				 * C99 says that the UTC offset must
509				 * be computed by looking only at
510				 * tm_isdst. This requirement is
511				 * incorrect, since it means the code
512				 * must rely on magic (in this case
513				 * altzone and timezone), and the
514				 * magic might not have the correct
515				 * offset. Doing things correctly is
516				 * tricky and requires disobeying C99;
517				 * see GNU C strftime for details.
518				 * For now, punt and conform to the
519				 * standard, even though it's incorrect.
520				 *
521				 * C99 says that %z must be replaced by the
522				 * empty string if the time zone is not
523				 * determinable, so output nothing if the
524				 * appropriate variables are not available.
525				 */
526				if (t->tm_isdst == 0)
527#ifdef USG_COMPAT
528					diff = -timezone;
529#else /* !defined USG_COMPAT */
530					continue;
531#endif /* !defined USG_COMPAT */
532				else
533#ifdef ALTZONE
534					diff = -altzone;
535#else /* !defined ALTZONE */
536					continue;
537#endif /* !defined ALTZONE */
538#endif /* !defined TM_GMTOFF */
539				if (diff < 0) {
540					sign = "-";
541					diff = -diff;
542				} else
543					sign = "+";
544				pt = _add(sign, pt, ptlim);
545				diff /= SECSPERMIN;
546				diff = (diff / MINSPERHOUR) * 100 +
547					(diff % MINSPERHOUR);
548				pt = _conv(diff,
549					fmt_padding[PAD_FMT_YEAR][PadIndex],
550					pt, ptlim, loc);
551				}
552				continue;
553			case '+':
554				pt = _fmt(tptr->date_fmt, t, pt, ptlim,
555					warnp, loc);
556				continue;
557			case '-':
558				if (PadIndex != PAD_DEFAULT)
559					break;
560				PadIndex = PAD_LESS;
561				goto label;
562			case '_':
563				if (PadIndex != PAD_DEFAULT)
564					break;
565				PadIndex = PAD_SPACE;
566				goto label;
567			case '0':
568				if (PadIndex != PAD_DEFAULT)
569					break;
570				PadIndex = PAD_ZERO;
571				goto label;
572			case '%':
573			/*
574			 * X311J/88-090 (4.12.3.5): if conversion char is
575			 * undefined, behavior is undefined. Print out the
576			 * character itself as printf(3) also does.
577			 */
578			default:
579				break;
580			}
581		}
582		if (pt == ptlim)
583			break;
584		*pt++ = *format;
585	}
586	return (pt);
587}
588
589static char *
590_conv(const int n, const char * const format, char * const pt,
591    const char * const ptlim, locale_t  loc)
592{
593	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
594
595	(void) sprintf_l(buf, loc, format, n);
596	return _add(buf, pt, ptlim);
597}
598
599static char *
600_add(const char *str, char *pt, const char * const ptlim)
601{
602	while (pt < ptlim && (*pt = *str++) != '\0')
603		++pt;
604	return (pt);
605}
606
607/*
608 * POSIX and the C Standard are unclear or inconsistent about
609 * what %C and %y do if the year is negative or exceeds 9999.
610 * Use the convention that %C concatenated with %y yields the
611 * same output as %Y, and that %Y contains at least 4 bytes,
612 * with more only if necessary.
613 */
614
615static char *
616_yconv(const int a, const int b, const int convert_top, const int convert_yy,
617    char *pt, const char * const ptlim, locale_t  loc)
618{
619	register int	lead;
620	register int	trail;
621
622#define	DIVISOR	100
623	trail = a % DIVISOR + b % DIVISOR;
624	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
625	trail %= DIVISOR;
626	if (trail < 0 && lead > 0) {
627		trail += DIVISOR;
628		--lead;
629	} else if (lead < 0 && trail > 0) {
630		trail -= DIVISOR;
631		++lead;
632	}
633	if (convert_top) {
634		if (lead == 0 && trail < 0)
635			pt = _add("-0", pt, ptlim);
636		else	pt = _conv(lead, "%02d", pt, ptlim, loc);
637	}
638	if (convert_yy)
639		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt,
640		     ptlim, loc);
641	return (pt);
642}
643