strftime.c revision 30089
133965Sjdp/*
2218822Sdim * Copyright (c) 1989 The Regents of the University of California.
392828Sobrien * All rights reserved.
4218822Sdim *
5218822Sdim * Redistribution and use in source and binary forms are permitted
6218822Sdim * provided that the above copyright notice and this paragraph are
7218822Sdim * duplicated in all such forms and that any documentation,
8218822Sdim * advertising materials, and other materials related to such
9218822Sdim * distribution and use acknowledge that the software was developed
10218822Sdim * by the University of California, Berkeley.  The name of the
11218822Sdim * University may not be used to endorse or promote products derived
12218822Sdim * from this software without specific prior written permission.
13218822Sdim * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14218822Sdim * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15218822Sdim * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16218822Sdim */
17218822Sdim
18218822Sdim#ifdef LIBC_RCS
19218822Sdimstatic const char rcsid[] =
20218822Sdim	"$Id: strftime.c,v 1.18 1997/08/09 15:43:53 joerg Exp $";
21218822Sdim#endif
22218822Sdim
23218822Sdim#ifndef lint
24218822Sdim#ifndef NOID
25218822Sdimstatic const char	elsieid[] = "@(#)strftime.c	7.38";
26218822Sdim/*
27218822Sdim** Based on the UCB version with the ID appearing below.
28218822Sdim** This is ANSIish only when "multibyte character == plain character".
29218822Sdim*/
30218822Sdim#endif /* !defined NOID */
31218822Sdim#endif /* !defined lint */
32218822Sdim
33218822Sdim#include "private.h"
34218822Sdim
35218822Sdim#ifndef LIBC_SCCS
36218822Sdim#ifndef lint
37218822Sdimstatic const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
38218822Sdim#endif /* !defined lint */
39218822Sdim#endif /* !defined LIBC_SCCS */
40218822Sdim
41218822Sdim#include "tzfile.h"
42218822Sdim#include <fcntl.h>
43218822Sdim#include <sys/stat.h>
44218822Sdim#include "timelocal.h"
45218822Sdim
46218822Sdimstatic char *	_add P((const char *, char *, const char *));
47218822Sdimstatic char *	_conv P((int, const char *, char *, const char *));
48218822Sdimstatic char *	_fmt P((const char *, const struct tm *, char *, const char *));
49218822Sdimstatic char *   _secs P((const struct tm *, char *, const char *));
50218822Sdim
51218822Sdimsize_t strftime P((char *, size_t, const char *, const struct tm *));
52218822Sdim
53218822Sdimextern char *	tzname[];
54218822Sdim
55218822Sdimsize_t
56218822Sdimstrftime(s, maxsize, format, t)
57218822Sdim	char *const s;
58218822Sdim	const size_t maxsize;
59218822Sdim	const char *const format;
60218822Sdim	const struct tm *const t;
61218822Sdim{
62218822Sdim	char *p;
63218822Sdim
64218822Sdim	tzset();
65218822Sdim	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
66218822Sdim	if (p == s + maxsize)
67218822Sdim		return 0;
68218822Sdim	*p = '\0';
69218822Sdim	return p - s;
70218822Sdim}
71218822Sdim
72218822Sdimstatic char *
73218822Sdim_fmt(format, t, pt, ptlim)
74218822Sdim	const char *format;
75218822Sdim	const struct tm *const t;
76218822Sdim	char *pt;
77218822Sdim	const char *const ptlim;
78218822Sdim{
79218822Sdim	for ( ; *format; ++format) {
80218822Sdim		if (*format == '%') {
81130561Sobrienlabel:
82130561Sobrien			switch (*++format) {
83130561Sobrien			case '\0':
84130561Sobrien				--format;
85130561Sobrien				break;
86130561Sobrien			case 'A':
87130561Sobrien				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
88130561Sobrien					"?" : Locale->weekday[t->tm_wday],
89130561Sobrien					pt, ptlim);
90130561Sobrien				continue;
91130561Sobrien			case 'a':
92130561Sobrien				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
93130561Sobrien					"?" : Locale->wday[t->tm_wday],
94130561Sobrien					pt, ptlim);
95130561Sobrien				continue;
96130561Sobrien			case 'B':
97130561Sobrien				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
98130561Sobrien					"?" : Locale->month[t->tm_mon],
99130561Sobrien					pt, ptlim);
100130561Sobrien				continue;
101130561Sobrien			case 'b':
102130561Sobrien			case 'h':
103130561Sobrien				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
104130561Sobrien					"?" : Locale->mon[t->tm_mon],
105130561Sobrien					pt, ptlim);
106130561Sobrien				continue;
107130561Sobrien			case 'C':
108130561Sobrien				/*
109130561Sobrien				** %C used to do a...
110130561Sobrien				**	_fmt("%a %b %e %X %Y", t);
111130561Sobrien				** ...whereas now POSIX 1003.2 calls for
112130561Sobrien				** something completely different.
113130561Sobrien				** (ado, 5/24/93)
114130561Sobrien				*/
115130561Sobrien				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
116130561Sobrien					"%02d", pt, ptlim);
117130561Sobrien				continue;
118130561Sobrien			case 'c':
119130561Sobrien				pt = _fmt(Locale->c_fmt, t, pt, ptlim);
120130561Sobrien				continue;
121130561Sobrien			case 'D':
122130561Sobrien				pt = _fmt("%m/%d/%y", t, pt, ptlim);
123130561Sobrien				continue;
124130561Sobrien			case 'd':
125130561Sobrien				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
126130561Sobrien				continue;
127130561Sobrien			case 'E':
128130561Sobrien			case 'O':
129130561Sobrien				/*
130130561Sobrien				** POSIX locale extensions, a la
131130561Sobrien				** Arnold Robbins' strftime version 3.0.
132130561Sobrien				** The sequences
133130561Sobrien				**	%Ec %EC %Ex %Ey %EY
134130561Sobrien				**	%Od %oe %OH %OI %Om %OM
135104834Sobrien				**	%OS %Ou %OU %OV %Ow %OW %Oy
136104834Sobrien				** are supposed to provide alternate
137130561Sobrien				** representations.
138130561Sobrien				** (ado, 5/24/93)
139104834Sobrien				*/
140130561Sobrien				goto label;
141104834Sobrien			case 'e':
142130561Sobrien				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
143130561Sobrien				continue;
144104834Sobrien			case 'H':
145130561Sobrien				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
146130561Sobrien				continue;
147130561Sobrien			case 'I':
148104834Sobrien				pt = _conv((t->tm_hour % 12) ?
14992828Sobrien					(t->tm_hour % 12) : 12,
15092828Sobrien					"%02d", pt, ptlim);
151130561Sobrien				continue;
15291041Sobrien			case 'j':
153130561Sobrien				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
15491041Sobrien				continue;
155130561Sobrien			case 'k':
156130561Sobrien				/*
157130561Sobrien				** This used to be...
158130561Sobrien				**	_conv(t->tm_hour % 12 ?
15933965Sjdp				**		t->tm_hour % 12 : 12, 2, ' ');
160130561Sobrien				** ...and has been changed to the below to
161130561Sobrien				** match SunOS 4.1.1 and Arnold Robbins'
16289857Sobrien				** strftime version 3.0.  That is, "%k" and
163130561Sobrien				** "%l" have been swapped.
164130561Sobrien				** (ado, 5/24/93)
16589857Sobrien				*/
166130561Sobrien				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
167130561Sobrien				continue;
16889857Sobrien#ifdef KITCHEN_SINK
169130561Sobrien			case 'K':
170130561Sobrien				/*
171130561Sobrien				** After all this time, still unclaimed!
17289857Sobrien				*/
17377298Sobrien				pt = _add("kitchen sink", pt, ptlim);
17477298Sobrien				continue;
175130561Sobrien#endif /* defined KITCHEN_SINK */
17677298Sobrien			case 'l':
177130561Sobrien				/*
17877298Sobrien				** This used to be...
179130561Sobrien				**	_conv(t->tm_hour, 2, ' ');
18077298Sobrien				** ...and has been changed to the below to
181130561Sobrien				** match SunOS 4.1.1 and Arnold Robbin's
18277298Sobrien				** strftime version 3.0.  That is, "%k" and
183130561Sobrien				** "%l" have been swapped.
18477298Sobrien				** (ado, 5/24/93)
185130561Sobrien				*/
18677298Sobrien				pt = _conv((t->tm_hour % 12) ?
187130561Sobrien					(t->tm_hour % 12) : 12,
18877298Sobrien					"%2d", pt, ptlim);
189130561Sobrien				continue;
19077298Sobrien			case 'M':
191130561Sobrien				pt = _conv(t->tm_min, "%02d", pt, ptlim);
19277298Sobrien				continue;
193130561Sobrien			case 'm':
194130561Sobrien				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
195130561Sobrien				continue;
196130561Sobrien			case 'n':
19760484Sobrien				pt = _add("\n", pt, ptlim);
19860484Sobrien				continue;
199130561Sobrien			case 'p':
200130561Sobrien				pt = _add((t->tm_hour >= 12) ?
20161843Sobrien					Locale->pm :
202130561Sobrien					Locale->am,
20360484Sobrien					pt, ptlim);
204130561Sobrien				continue;
20560484Sobrien			case 'R':
206130561Sobrien				pt = _fmt("%H:%M", t, pt, ptlim);
20760484Sobrien				continue;
208130561Sobrien			case 'r':
20960484Sobrien				pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
210130561Sobrien				continue;
21160484Sobrien			case 'S':
212130561Sobrien				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
21360484Sobrien				continue;
214130561Sobrien			case 's':
21560484Sobrien				pt = _secs(t, pt, ptlim);
216130561Sobrien				continue;
21760484Sobrien			case 'T':
218130561Sobrien				pt = _fmt("%H:%M:%S", t, pt, ptlim);
21960484Sobrien				continue;
220130561Sobrien			case 't':
221130561Sobrien				pt = _add("\t", pt, ptlim);
22260484Sobrien				continue;
223130561Sobrien			case 'U':
22460484Sobrien				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
225130561Sobrien					"%02d", pt, ptlim);
22660484Sobrien				continue;
227130561Sobrien			case 'u':
22860484Sobrien				/*
229130561Sobrien				** From Arnold Robbins' strftime version 3.0:
230130561Sobrien				** "ISO 8601: Weekday as a decimal number
231130561Sobrien				** [1 (Monday) - 7]"
232130561Sobrien				** (ado, 5/24/93)
23360484Sobrien				*/
234130561Sobrien				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
23560484Sobrien					"%d", pt, ptlim);
236130561Sobrien				continue;
23760484Sobrien			case 'V':	/* ISO 8601 week number */
238130561Sobrien			case 'G':	/* ISO 8601 year (four digits) */
23960484Sobrien			case 'g':	/* ISO 8601 year (two digits) */
240130561Sobrien/*
24160484Sobrien** From Arnold Robbins' strftime version 3.0:  "the week number of the
242130561Sobrien** year (the first Monday as the first day of week 1) as a decimal number
24361843Sobrien** (01-53)."
24438889Sjdp** (ado, 1993-05-24)
24538889Sjdp**
246130561Sobrien** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
24738889Sjdp** "Week 01 of a year is per definition the first week which has the
248130561Sobrien** Thursday in this year, which is equivalent to the week which contains
249130561Sobrien** the fourth day of January. In other words, the first week of a new year
25038889Sjdp** is the week which has the majority of its days in the new year. Week 01
251130561Sobrien** might also contain days from the previous year and the week before week
25238889Sjdp** 01 of a year is the last week (52 or 53) of the previous year even if
253130561Sobrien** it contains days from the new year. A week starts with Monday (day 1)
254130561Sobrien** and ends with Sunday (day 7).  For example, the first week of the year
25538889Sjdp** 1997 lasts from 1996-12-30 to 1997-01-05..."
256130561Sobrien** (ado, 1996-01-02)
25738889Sjdp*/
25833965Sjdp				{
25933965Sjdp					int	year;
260130561Sobrien					int	yday;
26133965Sjdp					int	wday;
262130561Sobrien					int	w;
26333965Sjdp
264130561Sobrien					year = t->tm_year + TM_YEAR_BASE;
26533965Sjdp					yday = t->tm_yday;
266130561Sobrien					wday = t->tm_wday;
26733965Sjdp					for ( ; ; ) {
268130561Sobrien						int	len;
269130561Sobrien						int	bot;
27033965Sjdp						int	top;
271130561Sobrien
272130561Sobrien						len = isleap(year) ?
273130561Sobrien							DAYSPERLYEAR :
274130561Sobrien							DAYSPERNYEAR;
27533965Sjdp						/*
276130561Sobrien						** What yday (-3 ... 3) does
27733965Sjdp						** the ISO year begin on?
278130561Sobrien						*/
279130561Sobrien						bot = ((yday + 11 - wday) %
28033965Sjdp							DAYSPERWEEK) - 3;
281130561Sobrien						/*
282130561Sobrien						** What yday does the NEXT
28333965Sjdp						** ISO year begin on?
28433965Sjdp						*/
28533965Sjdp						top = bot -
286130561Sobrien							(len % DAYSPERWEEK);
287130561Sobrien						if (top < -3)
288130561Sobrien							top += DAYSPERWEEK;
289130561Sobrien						top += len;
29033965Sjdp						if (yday >= top) {
291130561Sobrien							++year;
29233965Sjdp							w = 1;
293130561Sobrien							break;
29433965Sjdp						}
295130561Sobrien						if (yday >= bot) {
29633965Sjdp							w = 1 + ((yday - bot) /
297130561Sobrien								DAYSPERWEEK);
29833965Sjdp							break;
299130561Sobrien						}
30033965Sjdp						--year;
301130561Sobrien						yday += isleap(year) ?
302130561Sobrien							DAYSPERLYEAR :
303130561Sobrien							DAYSPERNYEAR;
304130561Sobrien					}
30533965Sjdp#ifdef XPG4_1994_04_09
306130561Sobrien					if ((w == 52
30733965Sjdp					     && t->tm_mon == TM_JANUARY)
30833965Sjdp					    || (w == 1
30933965Sjdp						&& t->tm_mon == TM_DECEMBER))
310130561Sobrien						w = 53;
31133965Sjdp#endif /* defined XPG4_1994_04_09 */
312130561Sobrien					if (*format == 'V')
313130561Sobrien						pt = _conv(w, "%02d",
314130561Sobrien							pt, ptlim);
31533965Sjdp					else if (*format == 'g') {
316130561Sobrien						pt = _conv(year % 100, "%02d",
31733965Sjdp							pt, ptlim);
318130561Sobrien					} else	pt = _conv(year, "%04d",
31933965Sjdp							pt, ptlim);
320130561Sobrien				}
32133965Sjdp				continue;
32233965Sjdp			case 'v':
32333965Sjdp				/*
324130561Sobrien				** From Arnold Robbins' strftime version 3.0:
32533965Sjdp				** "date as dd-bbb-YYYY"
326130561Sobrien				** (ado, 5/24/93)
32733965Sjdp				*/
328130561Sobrien				pt = _fmt("%e-%b-%Y", t, pt, ptlim);
329130561Sobrien				continue;
33033965Sjdp			case 'W':
331130561Sobrien				pt = _conv((t->tm_yday + 7 -
33233965Sjdp					(t->tm_wday ?
333130561Sobrien					(t->tm_wday - 1) : 6)) / 7,
334130561Sobrien					"%02d", pt, ptlim);
335130561Sobrien				continue;
336130561Sobrien			case 'w':
33733965Sjdp				pt = _conv(t->tm_wday, "%d", pt, ptlim);
338130561Sobrien				continue;
33933965Sjdp			case 'X':
340130561Sobrien				pt = _fmt(Locale->X_fmt, t, pt, ptlim);
341130561Sobrien				continue;
34233965Sjdp			case 'x':
343130561Sobrien				pt = _fmt(Locale->x_fmt, t, pt, ptlim);
34433965Sjdp				continue;
345130561Sobrien			case 'y':
346130561Sobrien				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
34733965Sjdp					"%02d", pt, ptlim);
348130561Sobrien				continue;
349130561Sobrien			case 'Y':
35033965Sjdp				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
351130561Sobrien					pt, ptlim);
352130561Sobrien				continue;
35333965Sjdp			case 'Z':
354130561Sobrien				if (t->tm_zone != NULL)
35533965Sjdp					pt = _add(t->tm_zone, pt, ptlim);
35633965Sjdp				else
35733965Sjdp				if (t->tm_isdst == 0 || t->tm_isdst == 1) {
358130561Sobrien					pt = _add(tzname[t->tm_isdst],
35933965Sjdp						pt, ptlim);
360130561Sobrien				} else  pt = _add("?", pt, ptlim);
36133965Sjdp				continue;
362130561Sobrien			case '+':
363130561Sobrien				pt = _fmt(Locale->date_fmt, t, pt, ptlim);
364130561Sobrien				continue;
36533965Sjdp			case '%':
366130561Sobrien			/*
367130561Sobrien			 * X311J/88-090 (4.12.3.5): if conversion char is
368130561Sobrien			 * undefined, behavior is undefined.  Print out the
369130561Sobrien			 * character itself as printf(3) also does.
370130561Sobrien			 */
37133965Sjdp			default:
372130561Sobrien				break;
373130561Sobrien			}
374130561Sobrien		}
37533965Sjdp		if (pt == ptlim)
376130561Sobrien			break;
377130561Sobrien		*pt++ = *format;
378130561Sobrien	}
379130561Sobrien	return pt;
38033965Sjdp}
381130561Sobrien
38233965Sjdpstatic char *
383130561Sobrien_conv(n, format, pt, ptlim)
384130561Sobrien	const int n;
38533965Sjdp	const char *const format;
386130561Sobrien	char *const pt;
387130561Sobrien	const char *const ptlim;
388130561Sobrien{
389130561Sobrien	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
39033965Sjdp
391130561Sobrien	(void) sprintf(buf, format, n);
392130561Sobrien	return _add(buf, pt, ptlim);
393130561Sobrien}
394130561Sobrien
39533965Sjdpstatic char *
39633965Sjdp_secs(t, pt, ptlim)
39733965Sjdp	const struct tm *t;
398130561Sobrien	char *pt;
39933965Sjdp	const char *ptlim;
400130561Sobrien{
401130561Sobrien	char    buf[INT_STRLEN_MAXIMUM(int) + 1];
402130561Sobrien	register time_t s;
403130561Sobrien	struct tm tmp;
404130561Sobrien
40533965Sjdp	/* Make a copy, mktime(3) modifies the tm struct. */
406130561Sobrien	tmp = *t;
407130561Sobrien	s = mktime(&tmp);
408130561Sobrien	(void) sprintf(buf, "%ld", s);
40933965Sjdp	return _add(buf, pt, ptlim);
410130561Sobrien}
411130561Sobrien
41233965Sjdpstatic char *
413130561Sobrien_add(str, pt, ptlim)
414130561Sobrien	const char *str;
415130561Sobrien	char *pt;
416130561Sobrien	const char *const ptlim;
41733965Sjdp{
418130561Sobrien	while (pt < ptlim && (*pt = *str++) != '\0')
419130561Sobrien		++pt;
420130561Sobrien	return pt;
421130561Sobrien}
422130561Sobrien