strftime.c revision 56756
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#ifdef LIBC_RCS
19static const char rcsid[] =
20  "$FreeBSD: head/lib/libc/stdtime/strftime.c 56756 2000-01-28 17:40:42Z joerg $";
21#endif
22
23#ifndef lint
24#ifndef NOID
25static const char	elsieid[] = "@(#)strftime.c	7.38";
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 "private.h"
34
35#ifndef LIBC_SCCS
36#ifndef lint
37static const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
38#endif /* !defined lint */
39#endif /* !defined LIBC_SCCS */
40
41#include "tzfile.h"
42#include <fcntl.h>
43#include <sys/stat.h>
44#include "timelocal.h"
45
46static char *	_add P((const char *, char *, const char *));
47static char *	_conv P((int, const char *, char *, const char *));
48static char *	_fmt P((const char *, const struct tm *, char *, const char *));
49
50size_t strftime P((char *, size_t, const char *, const struct tm *));
51
52extern char *	tzname[];
53
54size_t
55strftime(s, maxsize, format, t)
56	char *const s;
57	const size_t maxsize;
58	const char *const format;
59	const struct tm *const t;
60{
61	char *p;
62
63	tzset();
64	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
65	if (p == s + maxsize)
66		return 0;
67	*p = '\0';
68	return p - s;
69}
70
71static char *
72_fmt(format, t, pt, ptlim)
73	const char *format;
74	const struct tm *const t;
75	char *pt;
76	const char *const ptlim;
77{
78	int Ealternative, Oalternative;
79
80	for ( ; *format; ++format) {
81		if (*format == '%') {
82			Ealternative = 0;
83			Oalternative = 0;
84label:
85			switch (*++format) {
86			case '\0':
87				--format;
88				break;
89			case 'A':
90				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
91					"?" : Locale->weekday[t->tm_wday],
92					pt, ptlim);
93				continue;
94			case 'a':
95				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
96					"?" : Locale->wday[t->tm_wday],
97					pt, ptlim);
98				continue;
99			case 'B':
100				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
101					"?" : (Oalternative ? Locale->alt_month :
102					Locale->month)[t->tm_mon],
103					pt, ptlim);
104				continue;
105			case 'b':
106			case 'h':
107				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
108					"?" : Locale->mon[t->tm_mon],
109					pt, ptlim);
110				continue;
111			case 'C':
112				/*
113				** %C used to do a...
114				**	_fmt("%a %b %e %X %Y", t);
115				** ...whereas now POSIX 1003.2 calls for
116				** something completely different.
117				** (ado, 5/24/93)
118				*/
119				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
120					"%02d", pt, ptlim);
121				continue;
122			case 'c':
123				pt = _fmt(Locale->c_fmt, t, pt, ptlim);
124				continue;
125			case 'D':
126				pt = _fmt("%m/%d/%y", t, pt, ptlim);
127				continue;
128			case 'd':
129				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
130				continue;
131			case 'E':
132				if (Ealternative || Oalternative)
133					break;
134				Ealternative++;
135				goto label;
136			case 'O':
137				/*
138				** POSIX locale extensions, a la
139				** Arnold Robbins' strftime version 3.0.
140				** The sequences
141				**      %Ec %EC %Ex %EX %Ey %EY
142				**	%Od %oe %OH %OI %Om %OM
143				**	%OS %Ou %OU %OV %Ow %OW %Oy
144				** are supposed to provide alternate
145				** representations.
146				** (ado, 5/24/93)
147				**
148				** FreeBSD extensions
149				**      %OB %Ef %EF
150				*/
151				if (Ealternative || Oalternative)
152					break;
153				Oalternative++;
154				goto label;
155			case 'e':
156				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
157				continue;
158			case 'f':
159				if (!Ealternative)
160					break;
161				pt = _fmt(Locale->Ef_fmt, t, pt, ptlim);
162				continue;
163			case 'F':
164				if (!Ealternative)
165					break;
166				pt = _fmt(Locale->EF_fmt, t, pt, ptlim);
167				continue;
168			case 'H':
169				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
170				continue;
171			case 'I':
172				pt = _conv((t->tm_hour % 12) ?
173					(t->tm_hour % 12) : 12,
174					"%02d", pt, ptlim);
175				continue;
176			case 'j':
177				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
178				continue;
179			case 'k':
180				/*
181				** This used to be...
182				**	_conv(t->tm_hour % 12 ?
183				**		t->tm_hour % 12 : 12, 2, ' ');
184				** ...and has been changed to the below to
185				** match SunOS 4.1.1 and Arnold Robbins'
186				** strftime version 3.0.  That is, "%k" and
187				** "%l" have been swapped.
188				** (ado, 5/24/93)
189				*/
190				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
191				continue;
192#ifdef KITCHEN_SINK
193			case 'K':
194				/*
195				** After all this time, still unclaimed!
196				*/
197				pt = _add("kitchen sink", pt, ptlim);
198				continue;
199#endif /* defined KITCHEN_SINK */
200			case 'l':
201				/*
202				** This used to be...
203				**	_conv(t->tm_hour, 2, ' ');
204				** ...and has been changed to the below to
205				** match SunOS 4.1.1 and Arnold Robbin's
206				** strftime version 3.0.  That is, "%k" and
207				** "%l" have been swapped.
208				** (ado, 5/24/93)
209				*/
210				pt = _conv((t->tm_hour % 12) ?
211					(t->tm_hour % 12) : 12,
212					"%2d", pt, ptlim);
213				continue;
214			case 'M':
215				pt = _conv(t->tm_min, "%02d", pt, ptlim);
216				continue;
217			case 'm':
218				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
219				continue;
220			case 'n':
221				pt = _add("\n", pt, ptlim);
222				continue;
223			case 'p':
224				pt = _add((t->tm_hour >= 12) ?
225					Locale->pm :
226					Locale->am,
227					pt, ptlim);
228				continue;
229			case 'R':
230				pt = _fmt("%H:%M", t, pt, ptlim);
231				continue;
232			case 'r':
233				pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
234				continue;
235			case 'S':
236				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
237				continue;
238			case 's':
239				{
240					struct tm	tm;
241					char		buf[INT_STRLEN_MAXIMUM(
242								time_t) + 1];
243					time_t		mkt;
244
245					tm = *t;
246					mkt = mktime(&tm);
247					if (TYPE_SIGNED(time_t))
248						(void) sprintf(buf, "%ld",
249							(long) mkt);
250					else	(void) sprintf(buf, "%lu",
251							(unsigned long) mkt);
252					pt = _add(buf, pt, ptlim);
253				}
254				continue;
255			case 'T':
256				pt = _fmt("%H:%M:%S", t, pt, ptlim);
257				continue;
258			case 't':
259				pt = _add("\t", pt, ptlim);
260				continue;
261			case 'U':
262				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
263					"%02d", pt, ptlim);
264				continue;
265			case 'u':
266				/*
267				** From Arnold Robbins' strftime version 3.0:
268				** "ISO 8601: Weekday as a decimal number
269				** [1 (Monday) - 7]"
270				** (ado, 5/24/93)
271				*/
272				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
273					"%d", pt, ptlim);
274				continue;
275			case 'V':	/* ISO 8601 week number */
276			case 'G':	/* ISO 8601 year (four digits) */
277			case 'g':	/* ISO 8601 year (two digits) */
278/*
279** From Arnold Robbins' strftime version 3.0:  "the week number of the
280** year (the first Monday as the first day of week 1) as a decimal number
281** (01-53)."
282** (ado, 1993-05-24)
283**
284** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
285** "Week 01 of a year is per definition the first week which has the
286** Thursday in this year, which is equivalent to the week which contains
287** the fourth day of January. In other words, the first week of a new year
288** is the week which has the majority of its days in the new year. Week 01
289** might also contain days from the previous year and the week before week
290** 01 of a year is the last week (52 or 53) of the previous year even if
291** it contains days from the new year. A week starts with Monday (day 1)
292** and ends with Sunday (day 7).  For example, the first week of the year
293** 1997 lasts from 1996-12-30 to 1997-01-05..."
294** (ado, 1996-01-02)
295*/
296				{
297					int	year;
298					int	yday;
299					int	wday;
300					int	w;
301
302					year = t->tm_year + TM_YEAR_BASE;
303					yday = t->tm_yday;
304					wday = t->tm_wday;
305					for ( ; ; ) {
306						int	len;
307						int	bot;
308						int	top;
309
310						len = isleap(year) ?
311							DAYSPERLYEAR :
312							DAYSPERNYEAR;
313						/*
314						** What yday (-3 ... 3) does
315						** the ISO year begin on?
316						*/
317						bot = ((yday + 11 - wday) %
318							DAYSPERWEEK) - 3;
319						/*
320						** What yday does the NEXT
321						** ISO year begin on?
322						*/
323						top = bot -
324							(len % DAYSPERWEEK);
325						if (top < -3)
326							top += DAYSPERWEEK;
327						top += len;
328						if (yday >= top) {
329							++year;
330							w = 1;
331							break;
332						}
333						if (yday >= bot) {
334							w = 1 + ((yday - bot) /
335								DAYSPERWEEK);
336							break;
337						}
338						--year;
339						yday += isleap(year) ?
340							DAYSPERLYEAR :
341							DAYSPERNYEAR;
342					}
343#ifdef XPG4_1994_04_09
344					if ((w == 52
345					     && t->tm_mon == TM_JANUARY)
346					    || (w == 1
347						&& t->tm_mon == TM_DECEMBER))
348						w = 53;
349#endif /* defined XPG4_1994_04_09 */
350					if (*format == 'V')
351						pt = _conv(w, "%02d",
352							pt, ptlim);
353					else if (*format == 'g') {
354						pt = _conv(year % 100, "%02d",
355							pt, ptlim);
356					} else	pt = _conv(year, "%04d",
357							pt, ptlim);
358				}
359				continue;
360			case 'v':
361				/*
362				** From Arnold Robbins' strftime version 3.0:
363				** "date as dd-bbb-YYYY"
364				** (ado, 5/24/93)
365				*/
366				pt = _fmt("%e-%b-%Y", t, pt, ptlim);
367				continue;
368			case 'W':
369				pt = _conv((t->tm_yday + 7 -
370					(t->tm_wday ?
371					(t->tm_wday - 1) : 6)) / 7,
372					"%02d", pt, ptlim);
373				continue;
374			case 'w':
375				pt = _conv(t->tm_wday, "%d", pt, ptlim);
376				continue;
377			case 'X':
378				pt = _fmt(Locale->X_fmt, t, pt, ptlim);
379				continue;
380			case 'x':
381				pt = _fmt(Locale->x_fmt, t, pt, ptlim);
382				continue;
383			case 'y':
384				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
385					"%02d", pt, ptlim);
386				continue;
387			case 'Y':
388				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
389					pt, ptlim);
390				continue;
391			case 'Z':
392				if (t->tm_zone != NULL)
393					pt = _add(t->tm_zone, pt, ptlim);
394				else
395				if (t->tm_isdst == 0 || t->tm_isdst == 1) {
396					pt = _add(tzname[t->tm_isdst],
397						pt, ptlim);
398				} else  pt = _add("?", pt, ptlim);
399				continue;
400			case 'z':
401				{
402					long absoff;
403					if (t->tm_gmtoff >= 0) {
404						absoff = t->tm_gmtoff;
405						pt = _add("+", pt, ptlim);
406					} else {
407						absoff = -t->tm_gmtoff;
408						pt = _add("-", pt, ptlim);
409					}
410					pt = _conv(absoff / 3600, "%02d",
411						pt, ptlim);
412					pt = _conv((absoff % 3600) / 60, "%02d",
413						pt, ptlim);
414				};
415				continue;
416			case '+':
417				pt = _fmt(Locale->date_fmt, t, pt, ptlim);
418				continue;
419			case '%':
420			/*
421			 * X311J/88-090 (4.12.3.5): if conversion char is
422			 * undefined, behavior is undefined.  Print out the
423			 * character itself as printf(3) also does.
424			 */
425			default:
426				break;
427			}
428		}
429		if (pt == ptlim)
430			break;
431		*pt++ = *format;
432	}
433	return pt;
434}
435
436static char *
437_conv(n, format, pt, ptlim)
438	const int n;
439	const char *const format;
440	char *const pt;
441	const char *const ptlim;
442{
443	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
444
445	(void) sprintf(buf, format, n);
446	return _add(buf, pt, ptlim);
447}
448
449static char *
450_add(str, pt, ptlim)
451	const char *str;
452	char *pt;
453	const char *const ptlim;
454{
455	while (pt < ptlim && (*pt = *str++) != '\0')
456		++pt;
457	return pt;
458}
459