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