strftime.c revision 97423
1196200Sscottl/*
2196200Sscottl * Copyright (c) 1989 The Regents of the University of California.
3196200Sscottl * All rights reserved.
4196200Sscottl *
5196200Sscottl * Redistribution and use in source and binary forms are permitted
6196200Sscottl * provided that the above copyright notice and this paragraph are
7196200Sscottl * duplicated in all such forms and that any documentation,
8196200Sscottl * advertising materials, and other materials related to such
9196200Sscottl * distribution and use acknowledge that the software was developed
10196200Sscottl * by the University of California, Berkeley.  The name of the
11196200Sscottl * University may not be used to endorse or promote products derived
12196200Sscottl * from this software without specific prior written permission.
13196200Sscottl * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14196200Sscottl * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15196200Sscottl * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16196200Sscottl */
17196200Sscottl
18196200Sscottl#ifndef lint
19196200Sscottl#ifndef NOID
20196200Sscottlstatic const char	elsieid[] = "@(#)strftime.c	7.38";
21196200Sscottl/*
22196200Sscottl** Based on the UCB version with the ID appearing below.
23196200Sscottl** This is ANSIish only when "multibyte character == plain character".
24196200Sscottl*/
25196200Sscottl#endif /* !defined NOID */
26196200Sscottl#endif /* !defined lint */
27196200Sscottl
28196200Sscottl#include "namespace.h"
29196200Sscottl#include "private.h"
30196200Sscottl
31196200Sscottl#if defined(LIBC_SCCS) && !defined(lint)
32196200Sscottlstatic const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
33196200Sscottl#endif /* LIBC_SCCS and not lint */
34196200Sscottl#include <sys/cdefs.h>
35237589Seadler__FBSDID("$FreeBSD: head/lib/libc/stdtime/strftime.c 97423 2002-05-28 20:12:42Z alfred $");
36196200Sscottl
37196200Sscottl#include "tzfile.h"
38196200Sscottl#include <fcntl.h>
39196200Sscottl#include <sys/stat.h>
40196200Sscottl#include "un-namespace.h"
41196200Sscottl#include "timelocal.h"
42196200Sscottl
43196200Sscottlstatic char *	_add(const char *, char *, const char *);
44196200Sscottlstatic char *	_conv(int, const char *, char *, const char *);
45196200Sscottlstatic char *	_fmt(const char *, const struct tm *, char *, const char *);
46196200Sscottl
47196200Sscottlsize_t strftime(char *, size_t, const char *, const struct tm *);
48196200Sscottl
49196200Sscottlextern char *	tzname[];
50196200Sscottl
51196200Sscottlsize_t
52196200Sscottlstrftime(s, maxsize, format, t)
53196200Sscottl	char *const s;
54196200Sscottl	const size_t maxsize;
55196200Sscottl	const char *const format;
56196200Sscottl	const struct tm *const t;
57196200Sscottl{
58196200Sscottl	char *p;
59196200Sscottl
60196200Sscottl	tzset();
61196200Sscottl	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
62196200Sscottl	if (p == s + maxsize)
63196200Sscottl		return 0;
64196200Sscottl	*p = '\0';
65196200Sscottl	return p - s;
66196200Sscottl}
67237589Seadler
68196200Sscottlstatic char *
69196200Sscottl_fmt(format, t, pt, ptlim)
70214396Sjhb	const char *format;
71196200Sscottl	const struct tm *const t;
72196200Sscottl	char *pt;
73196200Sscottl	const char *const ptlim;
74196200Sscottl{
75196200Sscottl	int Ealternative, Oalternative;
76196200Sscottl	struct lc_time_T *tptr = __get_current_time_locale();
77237589Seadler
78196200Sscottl	for ( ; *format; ++format) {
79214396Sjhb		if (*format == '%') {
80196200Sscottl			Ealternative = 0;
81214396Sjhb			Oalternative = 0;
82196200Sscottllabel:
83196200Sscottl			switch (*++format) {
84196200Sscottl			case '\0':
85214396Sjhb				--format;
86196200Sscottl				break;
87222899Sbz			case 'A':
88214396Sjhb				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
89196200Sscottl					"?" : tptr->weekday[t->tm_wday],
90196200Sscottl					pt, ptlim);
91196200Sscottl				continue;
92196200Sscottl			case 'a':
93196200Sscottl				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
94196200Sscottl					"?" : tptr->wday[t->tm_wday],
95196200Sscottl					pt, ptlim);
96196200Sscottl				continue;
97196200Sscottl			case 'B':
98196200Sscottl				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
99196200Sscottl					"?" : (Oalternative ? tptr->alt_month :
100196200Sscottl					tptr->month)[t->tm_mon],
101196200Sscottl					pt, ptlim);
102196200Sscottl				continue;
103196200Sscottl			case 'b':
104196200Sscottl			case 'h':
105196200Sscottl				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
106196200Sscottl					"?" : tptr->mon[t->tm_mon],
107196200Sscottl					pt, ptlim);
108196200Sscottl				continue;
109196200Sscottl			case 'C':
110196200Sscottl				/*
111196200Sscottl				** %C used to do a...
112196200Sscottl				**	_fmt("%a %b %e %X %Y", t);
113196200Sscottl				** ...whereas now POSIX 1003.2 calls for
114196200Sscottl				** something completely different.
115196200Sscottl				** (ado, 5/24/93)
116196200Sscottl				*/
117196200Sscottl				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
118196200Sscottl					"%02d", pt, ptlim);
119196200Sscottl				continue;
120196200Sscottl			case 'c':
121196200Sscottl				pt = _fmt(tptr->c_fmt, t, pt, ptlim);
122196200Sscottl				continue;
123196200Sscottl			case 'D':
124196200Sscottl				pt = _fmt("%m/%d/%y", t, pt, ptlim);
125196200Sscottl				continue;
126196200Sscottl			case 'd':
127196200Sscottl				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
128196200Sscottl				continue;
129196200Sscottl			case 'E':
130196200Sscottl				if (Ealternative || Oalternative)
131196200Sscottl					break;
132196200Sscottl				Ealternative++;
133196200Sscottl				goto label;
134196200Sscottl			case 'O':
135196200Sscottl				/*
136196200Sscottl				** POSIX locale extensions, a la
137196200Sscottl				** Arnold Robbins' strftime version 3.0.
138196200Sscottl				** The sequences
139196200Sscottl				**      %Ec %EC %Ex %EX %Ey %EY
140196200Sscottl				**	%Od %oe %OH %OI %Om %OM
141196200Sscottl				**	%OS %Ou %OU %OV %Ow %OW %Oy
142196200Sscottl				** are supposed to provide alternate
143196200Sscottl				** representations.
144196200Sscottl				** (ado, 5/24/93)
145196200Sscottl				**
146196200Sscottl				** FreeBSD extensions
147196200Sscottl				**      %OB %Ef %EF
148196200Sscottl				*/
149196200Sscottl				if (Ealternative || Oalternative)
150196200Sscottl					break;
151196200Sscottl				Oalternative++;
152196200Sscottl				goto label;
153196200Sscottl			case 'e':
154196200Sscottl				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
155196200Sscottl				continue;
156196200Sscottl			case 'F':
157196200Sscottl				pt = _fmt("%Y-%m-%d", t, pt, ptlim);
158196200Sscottl				continue;
159196200Sscottl			case 'H':
160196200Sscottl				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
161196200Sscottl				continue;
162196200Sscottl			case 'I':
163196200Sscottl				pt = _conv((t->tm_hour % 12) ?
164196200Sscottl					(t->tm_hour % 12) : 12,
165196200Sscottl					"%02d", pt, ptlim);
166196200Sscottl				continue;
167196200Sscottl			case 'j':
168196200Sscottl				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
169196200Sscottl				continue;
170196200Sscottl			case 'k':
171196200Sscottl				/*
172196200Sscottl				** This used to be...
173196200Sscottl				**	_conv(t->tm_hour % 12 ?
174196200Sscottl				**		t->tm_hour % 12 : 12, 2, ' ');
175196200Sscottl				** ...and has been changed to the below to
176196200Sscottl				** match SunOS 4.1.1 and Arnold Robbins'
177196200Sscottl				** strftime version 3.0.  That is, "%k" and
178196200Sscottl				** "%l" have been swapped.
179196200Sscottl				** (ado, 5/24/93)
180196200Sscottl				*/
181196200Sscottl				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
182196200Sscottl				continue;
183196200Sscottl#ifdef KITCHEN_SINK
184196200Sscottl			case 'K':
185196200Sscottl				/*
186196200Sscottl				** After all this time, still unclaimed!
187196200Sscottl				*/
188196200Sscottl				pt = _add("kitchen sink", pt, ptlim);
189196200Sscottl				continue;
190196200Sscottl#endif /* defined KITCHEN_SINK */
191196200Sscottl			case 'l':
192196200Sscottl				/*
193196200Sscottl				** This used to be...
194196200Sscottl				**	_conv(t->tm_hour, 2, ' ');
195196200Sscottl				** ...and has been changed to the below to
196196200Sscottl				** match SunOS 4.1.1 and Arnold Robbin's
197196200Sscottl				** strftime version 3.0.  That is, "%k" and
198196200Sscottl				** "%l" have been swapped.
199196200Sscottl				** (ado, 5/24/93)
200196200Sscottl				*/
201196200Sscottl				pt = _conv((t->tm_hour % 12) ?
202196200Sscottl					(t->tm_hour % 12) : 12,
203196200Sscottl					"%2d", pt, ptlim);
204196200Sscottl				continue;
205196200Sscottl			case 'M':
206196200Sscottl				pt = _conv(t->tm_min, "%02d", pt, ptlim);
207196200Sscottl				continue;
208196200Sscottl			case 'm':
209196200Sscottl				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
210196200Sscottl				continue;
211196200Sscottl			case 'n':
212196200Sscottl				pt = _add("\n", pt, ptlim);
213196200Sscottl				continue;
214196200Sscottl			case 'p':
215196200Sscottl				pt = _add((t->tm_hour >= 12) ?
216196200Sscottl					tptr->pm :
217196200Sscottl					tptr->am,
218196200Sscottl					pt, ptlim);
219196200Sscottl				continue;
220196200Sscottl			case 'R':
221196200Sscottl				pt = _fmt("%H:%M", t, pt, ptlim);
222196200Sscottl				continue;
223196200Sscottl			case 'r':
224196200Sscottl				pt = _fmt(tptr->ampm_fmt, t, pt, ptlim);
225196200Sscottl				continue;
226196200Sscottl			case 'S':
227196200Sscottl				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
228196200Sscottl				continue;
229196200Sscottl			case 's':
230196200Sscottl				{
231196200Sscottl					struct tm	tm;
232196200Sscottl					char		buf[INT_STRLEN_MAXIMUM(
233196200Sscottl								time_t) + 1];
234196200Sscottl					time_t		mkt;
235196200Sscottl
236196200Sscottl					tm = *t;
237196200Sscottl					mkt = mktime(&tm);
238196200Sscottl					if (TYPE_SIGNED(time_t))
239196200Sscottl						(void) sprintf(buf, "%ld",
240196200Sscottl							(long) mkt);
241196200Sscottl					else	(void) sprintf(buf, "%lu",
242196200Sscottl							(unsigned long) mkt);
243196200Sscottl					pt = _add(buf, pt, ptlim);
244196200Sscottl				}
245196200Sscottl				continue;
246196200Sscottl			case 'T':
247196200Sscottl				pt = _fmt("%H:%M:%S", t, pt, ptlim);
248196200Sscottl				continue;
249196200Sscottl			case 't':
250196200Sscottl				pt = _add("\t", pt, ptlim);
251196200Sscottl				continue;
252196200Sscottl			case 'U':
253196200Sscottl				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
254196200Sscottl					"%02d", pt, ptlim);
255196200Sscottl				continue;
256196200Sscottl			case 'u':
257196200Sscottl				/*
258196200Sscottl				** From Arnold Robbins' strftime version 3.0:
259196200Sscottl				** "ISO 8601: Weekday as a decimal number
260196200Sscottl				** [1 (Monday) - 7]"
261196200Sscottl				** (ado, 5/24/93)
262196200Sscottl				*/
263196200Sscottl				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
264196200Sscottl					"%d", pt, ptlim);
265196200Sscottl				continue;
266196200Sscottl			case 'V':	/* ISO 8601 week number */
267196200Sscottl			case 'G':	/* ISO 8601 year (four digits) */
268196200Sscottl			case 'g':	/* ISO 8601 year (two digits) */
269196200Sscottl/*
270196200Sscottl** From Arnold Robbins' strftime version 3.0:  "the week number of the
271196200Sscottl** year (the first Monday as the first day of week 1) as a decimal number
272196200Sscottl** (01-53)."
273196200Sscottl** (ado, 1993-05-24)
274196200Sscottl**
275196200Sscottl** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
276196200Sscottl** "Week 01 of a year is per definition the first week which has the
277196200Sscottl** Thursday in this year, which is equivalent to the week which contains
278196200Sscottl** the fourth day of January. In other words, the first week of a new year
279196200Sscottl** is the week which has the majority of its days in the new year. Week 01
280196200Sscottl** might also contain days from the previous year and the week before week
281196200Sscottl** 01 of a year is the last week (52 or 53) of the previous year even if
282196200Sscottl** it contains days from the new year. A week starts with Monday (day 1)
283196200Sscottl** and ends with Sunday (day 7).  For example, the first week of the year
284196200Sscottl** 1997 lasts from 1996-12-30 to 1997-01-05..."
285196200Sscottl** (ado, 1996-01-02)
286196200Sscottl*/
287196200Sscottl				{
288196200Sscottl					int	year;
289196200Sscottl					int	yday;
290196200Sscottl					int	wday;
291196200Sscottl					int	w;
292196200Sscottl
293196200Sscottl					year = t->tm_year + TM_YEAR_BASE;
294196200Sscottl					yday = t->tm_yday;
295196200Sscottl					wday = t->tm_wday;
296196200Sscottl					for ( ; ; ) {
297196200Sscottl						int	len;
298196200Sscottl						int	bot;
299196200Sscottl						int	top;
300196200Sscottl
301196200Sscottl						len = isleap(year) ?
302196200Sscottl							DAYSPERLYEAR :
303196200Sscottl							DAYSPERNYEAR;
304196200Sscottl						/*
305196200Sscottl						** What yday (-3 ... 3) does
306196200Sscottl						** the ISO year begin on?
307196200Sscottl						*/
308196200Sscottl						bot = ((yday + 11 - wday) %
309196200Sscottl							DAYSPERWEEK) - 3;
310196200Sscottl						/*
311196200Sscottl						** What yday does the NEXT
312196200Sscottl						** ISO year begin on?
313196200Sscottl						*/
314196200Sscottl						top = bot -
315196200Sscottl							(len % DAYSPERWEEK);
316196200Sscottl						if (top < -3)
317196200Sscottl							top += DAYSPERWEEK;
318196200Sscottl						top += len;
319196200Sscottl						if (yday >= top) {
320196200Sscottl							++year;
321196200Sscottl							w = 1;
322196200Sscottl							break;
323196200Sscottl						}
324196200Sscottl						if (yday >= bot) {
325196200Sscottl							w = 1 + ((yday - bot) /
326196200Sscottl								DAYSPERWEEK);
327196200Sscottl							break;
328196200Sscottl						}
329196200Sscottl						--year;
330196200Sscottl						yday += isleap(year) ?
331196200Sscottl							DAYSPERLYEAR :
332196200Sscottl							DAYSPERNYEAR;
333196200Sscottl					}
334196200Sscottl#ifdef XPG4_1994_04_09
335196200Sscottl					if ((w == 52
336196200Sscottl					     && t->tm_mon == TM_JANUARY)
337196200Sscottl					    || (w == 1
338196200Sscottl						&& t->tm_mon == TM_DECEMBER))
339196200Sscottl						w = 53;
340196200Sscottl#endif /* defined XPG4_1994_04_09 */
341196200Sscottl					if (*format == 'V')
342196200Sscottl						pt = _conv(w, "%02d",
343196200Sscottl							pt, ptlim);
344196200Sscottl					else if (*format == 'g') {
345196200Sscottl						pt = _conv(year % 100, "%02d",
346196200Sscottl							pt, ptlim);
347196200Sscottl					} else	pt = _conv(year, "%04d",
348196200Sscottl							pt, ptlim);
349196200Sscottl				}
350196200Sscottl				continue;
351196200Sscottl			case 'v':
352196200Sscottl				/*
353196200Sscottl				** From Arnold Robbins' strftime version 3.0:
354196200Sscottl				** "date as dd-bbb-YYYY"
355196200Sscottl				** (ado, 5/24/93)
356196200Sscottl				*/
357196200Sscottl				pt = _fmt("%e-%b-%Y", t, pt, ptlim);
358196200Sscottl				continue;
359196200Sscottl			case 'W':
360196200Sscottl				pt = _conv((t->tm_yday + 7 -
361196200Sscottl					(t->tm_wday ?
362196200Sscottl					(t->tm_wday - 1) : 6)) / 7,
363196200Sscottl					"%02d", pt, ptlim);
364196200Sscottl				continue;
365196200Sscottl			case 'w':
366196200Sscottl				pt = _conv(t->tm_wday, "%d", pt, ptlim);
367222589Semaste				continue;
368222589Semaste			case 'X':
369196200Sscottl				pt = _fmt(tptr->X_fmt, t, pt, ptlim);
370196200Sscottl				continue;
371196200Sscottl			case 'x':
372196200Sscottl				pt = _fmt(tptr->x_fmt, t, pt, ptlim);
373196200Sscottl				continue;
374196200Sscottl			case 'y':
375196200Sscottl				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
376196200Sscottl					"%02d", pt, ptlim);
377196200Sscottl				continue;
378196200Sscottl			case 'Y':
379196200Sscottl				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
380196200Sscottl					pt, ptlim);
381196200Sscottl				continue;
382196200Sscottl			case 'Z':
383196200Sscottl				if (t->tm_zone != NULL)
384196200Sscottl					pt = _add(t->tm_zone, pt, ptlim);
385196200Sscottl				else
386196200Sscottl				if (t->tm_isdst == 0 || t->tm_isdst == 1) {
387196200Sscottl					pt = _add(tzname[t->tm_isdst],
388196200Sscottl						pt, ptlim);
389196200Sscottl				} else  pt = _add("?", pt, ptlim);
390196200Sscottl				continue;
391196200Sscottl			case 'z':
392196200Sscottl				{
393196200Sscottl					long absoff;
394196200Sscottl					if (t->tm_gmtoff >= 0) {
395196200Sscottl						absoff = t->tm_gmtoff;
396196200Sscottl						pt = _add("+", pt, ptlim);
397196200Sscottl					} else {
398196200Sscottl						absoff = -t->tm_gmtoff;
399196200Sscottl						pt = _add("-", pt, ptlim);
400196200Sscottl					}
401196200Sscottl					pt = _conv(absoff / 3600, "%02d",
402196200Sscottl						pt, ptlim);
403196200Sscottl					pt = _conv((absoff % 3600) / 60, "%02d",
404196200Sscottl						pt, ptlim);
405196200Sscottl				};
406196200Sscottl				continue;
407196200Sscottl			case '+':
408196200Sscottl				pt = _fmt(tptr->date_fmt, t, pt, ptlim);
409196200Sscottl				continue;
410196200Sscottl			case '%':
411196200Sscottl			/*
412196200Sscottl			 * X311J/88-090 (4.12.3.5): if conversion char is
413196200Sscottl			 * undefined, behavior is undefined.  Print out the
414196200Sscottl			 * character itself as printf(3) also does.
415196200Sscottl			 */
416196200Sscottl			default:
417196200Sscottl				break;
418196200Sscottl			}
419196200Sscottl		}
420196200Sscottl		if (pt == ptlim)
421196200Sscottl			break;
422196200Sscottl		*pt++ = *format;
423196200Sscottl	}
424196200Sscottl	return pt;
425196200Sscottl}
426196200Sscottl
427196200Sscottlstatic char *
428196200Sscottl_conv(n, format, pt, ptlim)
429196200Sscottl	const int n;
430196200Sscottl	const char *const format;
431196200Sscottl	char *const pt;
432196200Sscottl	const char *const ptlim;
433196200Sscottl{
434196200Sscottl	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
435196200Sscottl
436196200Sscottl	(void) sprintf(buf, format, n);
437196200Sscottl	return _add(buf, pt, ptlim);
438196200Sscottl}
439196200Sscottl
440196200Sscottlstatic char *
441196200Sscottl_add(str, pt, ptlim)
442229623Sjhb	const char *str;
443196200Sscottl	char *pt;
444196200Sscottl	const char *const ptlim;
445196200Sscottl{
446196200Sscottl	while (pt < ptlim && (*pt = *str++) != '\0')
447196200Sscottl		++pt;
448196200Sscottl	return pt;
449196200Sscottl}
450196200Sscottl