strftime.c revision 74412
1254721Semaste/*
2254721Semaste * Copyright (c) 1989 The Regents of the University of California.
3353358Sdim * All rights reserved.
4353358Sdim *
5353358Sdim * Redistribution and use in source and binary forms are permitted
6254721Semaste * provided that the above copyright notice and this paragraph are
7254721Semaste * duplicated in all such forms and that any documentation,
8254721Semaste * advertising materials, and other materials related to such
9254721Semaste * distribution and use acknowledge that the software was developed
10353358Sdim * by the University of California, Berkeley.  The name of the
11353358Sdim * University may not be used to endorse or promote products derived
12254721Semaste * from this software without specific prior written permission.
13360784Sdim * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14254721Semaste * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15254721Semaste * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16321369Sdim */
17321369Sdim
18254721Semaste#ifdef LIBC_RCS
19254721Semastestatic const char rcsid[] =
20254721Semaste  "$FreeBSD: head/lib/libc/stdtime/strftime.c 74412 2001-03-18 11:58:15Z ache $";
21254721Semaste#endif
22360784Sdim
23360784Sdim#ifndef lint
24360784Sdim#ifndef NOID
25360784Sdimstatic const char	elsieid[] = "@(#)strftime.c	7.38";
26360784Sdim/*
27360784Sdim** Based on the UCB version with the ID appearing below.
28360784Sdim** This is ANSIish only when "multibyte character == plain character".
29360784Sdim*/
30360784Sdim#endif /* !defined NOID */
31360784Sdim#endif /* !defined lint */
32360784Sdim
33360784Sdim#include "namespace.h"
34360784Sdim#include "private.h"
35360784Sdim
36360784Sdim#ifndef LIBC_SCCS
37360784Sdim#ifndef lint
38360784Sdimstatic const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
39360784Sdim#endif /* !defined lint */
40360784Sdim#endif /* !defined LIBC_SCCS */
41360784Sdim
42360784Sdim#include "tzfile.h"
43360784Sdim#include <fcntl.h>
44360784Sdim#include <sys/stat.h>
45360784Sdim#include "un-namespace.h"
46360784Sdim#include "timelocal.h"
47360784Sdim
48314564Sdimstatic char *	_add P((const char *, char *, const char *));
49360784Sdimstatic char *	_conv P((int, const char *, char *, const char *));
50353358Sdimstatic char *	_fmt P((const char *, const struct tm *, char *, const char *));
51353358Sdim
52254721Semastesize_t strftime P((char *, size_t, const char *, const struct tm *));
53360784Sdim
54360784Sdimextern char *	tzname[];
55360784Sdim
56360784Sdimsize_t
57360784Sdimstrftime(s, maxsize, format, t)
58360784Sdim	char *const s;
59314564Sdim	const size_t maxsize;
60353358Sdim	const char *const format;
61353358Sdim	const struct tm *const t;
62353358Sdim{
63353358Sdim	char *p;
64353358Sdim
65254721Semaste	tzset();
66254721Semaste	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
67360784Sdim	if (p == s + maxsize)
68314564Sdim		return 0;
69353358Sdim	*p = '\0';
70360784Sdim	return p - s;
71353358Sdim}
72353358Sdim
73353358Sdimstatic char *
74353358Sdim_fmt(format, t, pt, ptlim)
75353358Sdim	const char *format;
76353358Sdim	const struct tm *const t;
77254721Semaste	char *pt;
78254721Semaste	const char *const ptlim;
79360784Sdim{
80360784Sdim	int Ealternative, Oalternative;
81353358Sdim	struct lc_time_T *tptr = __get_current_time_locale();
82353358Sdim
83353358Sdim	for ( ; *format; ++format) {
84353358Sdim		if (*format == '%') {
85353358Sdim			Ealternative = 0;
86353358Sdim			Oalternative = 0;
87254721Semastelabel:
88360784Sdim			switch (*++format) {
89360784Sdim			case '\0':
90353358Sdim				--format;
91353358Sdim				break;
92314564Sdim			case 'A':
93353358Sdim				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
94254721Semaste					"?" : tptr->weekday[t->tm_wday],
95360784Sdim					pt, ptlim);
96360784Sdim				continue;
97314564Sdim			case 'a':
98254721Semaste				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
99314564Sdim					"?" : tptr->wday[t->tm_wday],
100353358Sdim					pt, ptlim);
101276479Sdim				continue;
102360784Sdim			case 'B':
103360784Sdim				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
104254721Semaste					"?" : (Oalternative ? tptr->alt_month :
105254721Semaste					tptr->month)[t->tm_mon],
106314564Sdim					pt, ptlim);
107353358Sdim				continue;
108353358Sdim			case 'b':
109360784Sdim			case 'h':
110254721Semaste				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
111254721Semaste					"?" : tptr->mon[t->tm_mon],
112314564Sdim					pt, ptlim);
113353358Sdim				continue;
114353358Sdim			case 'C':
115360784Sdim				/*
116254721Semaste				** %C used to do a...
117254721Semaste				**	_fmt("%a %b %e %X %Y", t);
118314564Sdim				** ...whereas now POSIX 1003.2 calls for
119360784Sdim				** something completely different.
120314564Sdim				** (ado, 5/24/93)
121314564Sdim				*/
122314564Sdim				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
123314564Sdim					"%02d", pt, ptlim);
124314564Sdim				continue;
125314564Sdim			case 'c':
126254721Semaste				pt = _fmt(tptr->c_fmt, t, pt, ptlim);
127254721Semaste				continue;
128360784Sdim			case 'D':
129360784Sdim				pt = _fmt("%m/%d/%y", t, pt, ptlim);
130360784Sdim				continue;
131360784Sdim			case 'd':
132360784Sdim				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
133360784Sdim				continue;
134360784Sdim			case 'E':
135360784Sdim				if (Ealternative || Oalternative)
136360784Sdim					break;
137360784Sdim				Ealternative++;
138360784Sdim				goto label;
139360784Sdim			case 'O':
140360784Sdim				/*
141360784Sdim				** POSIX locale extensions, a la
142360784Sdim				** Arnold Robbins' strftime version 3.0.
143314564Sdim				** The sequences
144360784Sdim				**      %Ec %EC %Ex %EX %Ey %EY
145314564Sdim				**	%Od %oe %OH %OI %Om %OM
146314564Sdim				**	%OS %Ou %OU %OV %Ow %OW %Oy
147314564Sdim				** are supposed to provide alternate
148314564Sdim				** representations.
149314564Sdim				** (ado, 5/24/93)
150314564Sdim				**
151254721Semaste				** FreeBSD extensions
152254721Semaste				**      %OB %Ef %EF
153360784Sdim				*/
154360784Sdim				if (Ealternative || Oalternative)
155360784Sdim					break;
156360784Sdim				Oalternative++;
157360784Sdim				goto label;
158360784Sdim			case 'e':
159360784Sdim				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
160360784Sdim				continue;
161360784Sdim			case 'f':
162360784Sdim				if (!Ealternative)
163360784Sdim					break;
164360784Sdim				pt = _fmt(*(tptr->md_order) == 'd' ?
165360784Sdim					  "%e %b" : "%b %e",
166360784Sdim					  t, pt, ptlim);
167360784Sdim				continue;
168314564Sdim			case 'F':
169353358Sdim				if (!Ealternative)
170353358Sdim					pt = _fmt("%Y-%m-%d", t, pt, ptlim);
171360784Sdim				else
172254721Semaste					pt = _fmt(*(tptr->md_order) == 'd' ?
173254721Semaste						  "%e %B" : "%B %e",
174314564Sdim						  t, pt, ptlim);
175353358Sdim				continue;
176353358Sdim			case 'H':
177353358Sdim				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
178360784Sdim				continue;
179254721Semaste			case 'I':
180254721Semaste				pt = _conv((t->tm_hour % 12) ?
181314564Sdim					(t->tm_hour % 12) : 12,
182353358Sdim					"%02d", pt, ptlim);
183353358Sdim				continue;
184353358Sdim			case 'j':
185360784Sdim				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
186254721Semaste				continue;
187254721Semaste			case 'k':
188314564Sdim				/*
189353358Sdim				** This used to be...
190353358Sdim				**	_conv(t->tm_hour % 12 ?
191360784Sdim				**		t->tm_hour % 12 : 12, 2, ' ');
192254721Semaste				** ...and has been changed to the below to
193254721Semaste				** match SunOS 4.1.1 and Arnold Robbins'
194314564Sdim				** strftime version 3.0.  That is, "%k" and
195353358Sdim				** "%l" have been swapped.
196353358Sdim				** (ado, 5/24/93)
197360784Sdim				*/
198254721Semaste				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
199254721Semaste				continue;
200314564Sdim#ifdef KITCHEN_SINK
201353358Sdim			case 'K':
202353358Sdim				/*
203353358Sdim				** After all this time, still unclaimed!
204360784Sdim				*/
205254721Semaste				pt = _add("kitchen sink", pt, ptlim);
206254721Semaste				continue;
207314564Sdim#endif /* defined KITCHEN_SINK */
208353358Sdim			case 'l':
209353358Sdim				/*
210353358Sdim				** This used to be...
211360784Sdim				**	_conv(t->tm_hour, 2, ' ');
212254721Semaste				** ...and has been changed to the below to
213254721Semaste				** match SunOS 4.1.1 and Arnold Robbin's
214314564Sdim				** strftime version 3.0.  That is, "%k" and
215360784Sdim				** "%l" have been swapped.
216254721Semaste				** (ado, 5/24/93)
217254721Semaste				*/
218314564Sdim				pt = _conv((t->tm_hour % 12) ?
219360784Sdim					(t->tm_hour % 12) : 12,
220254721Semaste					"%2d", pt, ptlim);
221254721Semaste				continue;
222314564Sdim			case 'M':
223360784Sdim				pt = _conv(t->tm_min, "%02d", pt, ptlim);
224254721Semaste				continue;
225254721Semaste			case 'm':
226314564Sdim				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
227360784Sdim				continue;
228254721Semaste			case 'n':
229254721Semaste				pt = _add("\n", pt, ptlim);
230314564Sdim				continue;
231353358Sdim			case 'p':
232353358Sdim				pt = _add((t->tm_hour >= 12) ?
233353358Sdim					tptr->pm :
234314564Sdim					tptr->am,
235254721Semaste					pt, ptlim);
236360784Sdim				continue;
237360784Sdim			case 'R':
238360784Sdim				pt = _fmt("%H:%M", t, pt, ptlim);
239360784Sdim				continue;
240360784Sdim			case 'r':
241360784Sdim				pt = _fmt(tptr->ampm_fmt, t, pt, ptlim);
242360784Sdim				continue;
243360784Sdim			case 'S':
244360784Sdim				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
245360784Sdim				continue;
246254721Semaste			case 's':
247360784Sdim				{
248360784Sdim					struct tm	tm;
249254721Semaste					char		buf[INT_STRLEN_MAXIMUM(
250360784Sdim								time_t) + 1];
251360784Sdim					time_t		mkt;
252254721Semaste
253314564Sdim					tm = *t;
254254721Semaste					mkt = mktime(&tm);
255254721Semaste					if (TYPE_SIGNED(time_t))
256314564Sdim						(void) sprintf(buf, "%ld",
257360784Sdim							(long) mkt);
258360784Sdim					else	(void) sprintf(buf, "%lu",
259353358Sdim							(unsigned long) mkt);
260314564Sdim					pt = _add(buf, pt, ptlim);
261254721Semaste				}
262254721Semaste				continue;
263314564Sdim			case 'T':
264360784Sdim				pt = _fmt("%H:%M:%S", t, pt, ptlim);
265360784Sdim				continue;
266353358Sdim			case 't':
267314564Sdim				pt = _add("\t", pt, ptlim);
268309124Sdim				continue;
269309124Sdim			case 'U':
270314564Sdim				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
271314564Sdim					"%02d", pt, ptlim);
272360784Sdim				continue;
273360784Sdim			case 'u':
274360784Sdim				/*
275360784Sdim				** From Arnold Robbins' strftime version 3.0:
276254721Semaste				** "ISO 8601: Weekday as a decimal number
277254721Semaste				** [1 (Monday) - 7]"
278314564Sdim				** (ado, 5/24/93)
279314564Sdim				*/
280360784Sdim				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
281360784Sdim					"%d", pt, ptlim);
282360784Sdim				continue;
283360784Sdim			case 'V':	/* ISO 8601 week number */
284360784Sdim			case 'G':	/* ISO 8601 year (four digits) */
285360784Sdim			case 'g':	/* ISO 8601 year (two digits) */
286360784Sdim/*
287360784Sdim** From Arnold Robbins' strftime version 3.0:  "the week number of the
288360784Sdim** year (the first Monday as the first day of week 1) as a decimal number
289360784Sdim** (01-53)."
290360784Sdim** (ado, 1993-05-24)
291360784Sdim**
292360784Sdim** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
293353358Sdim** "Week 01 of a year is per definition the first week which has the
294360784Sdim** Thursday in this year, which is equivalent to the week which contains
295360784Sdim** the fourth day of January. In other words, the first week of a new year
296360784Sdim** is the week which has the majority of its days in the new year. Week 01
297353358Sdim** might also contain days from the previous year and the week before week
298360784Sdim** 01 of a year is the last week (52 or 53) of the previous year even if
299360784Sdim** it contains days from the new year. A week starts with Monday (day 1)
300360784Sdim** and ends with Sunday (day 7).  For example, the first week of the year
301360784Sdim** 1997 lasts from 1996-12-30 to 1997-01-05..."
302309124Sdim** (ado, 1996-01-02)
303309124Sdim*/
304360784Sdim				{
305360784Sdim					int	year;
306360784Sdim					int	yday;
307360784Sdim					int	wday;
308360784Sdim					int	w;
309360784Sdim
310314564Sdim					year = t->tm_year + TM_YEAR_BASE;
311353358Sdim					yday = t->tm_yday;
312353358Sdim					wday = t->tm_wday;
313353358Sdim					for ( ; ; ) {
314360784Sdim						int	len;
315360784Sdim						int	bot;
316360784Sdim						int	top;
317360784Sdim
318360784Sdim						len = isleap(year) ?
319360784Sdim							DAYSPERLYEAR :
320360784Sdim							DAYSPERNYEAR;
321254721Semaste						/*
322254721Semaste						** What yday (-3 ... 3) does
323314564Sdim						** the ISO year begin on?
324353358Sdim						*/
325353358Sdim						bot = ((yday + 11 - wday) %
326353358Sdim							DAYSPERWEEK) - 3;
327314564Sdim						/*
328360784Sdim						** What yday does the NEXT
329314564Sdim						** ISO year begin on?
330314564Sdim						*/
331254721Semaste						top = bot -
332254721Semaste							(len % DAYSPERWEEK);
333314564Sdim						if (top < -3)
334353358Sdim							top += DAYSPERWEEK;
335353358Sdim						top += len;
336353358Sdim						if (yday >= top) {
337360784Sdim							++year;
338314564Sdim							w = 1;
339314564Sdim							break;
340254721Semaste						}
341254721Semaste						if (yday >= bot) {
342314564Sdim							w = 1 + ((yday - bot) /
343360784Sdim								DAYSPERWEEK);
344360784Sdim							break;
345360784Sdim						}
346360784Sdim						--year;
347360784Sdim						yday += isleap(year) ?
348254721Semaste							DAYSPERLYEAR :
349254721Semaste							DAYSPERNYEAR;
350314564Sdim					}
351314564Sdim#ifdef XPG4_1994_04_09
352353358Sdim					if ((w == 52
353353358Sdim					     && t->tm_mon == TM_JANUARY)
354353358Sdim					    || (w == 1
355353358Sdim						&& t->tm_mon == TM_DECEMBER))
356360784Sdim						w = 53;
357360784Sdim#endif /* defined XPG4_1994_04_09 */
358360784Sdim					if (*format == 'V')
359360784Sdim						pt = _conv(w, "%02d",
360254721Semaste							pt, ptlim);
361254721Semaste					else if (*format == 'g') {
362314564Sdim						pt = _conv(year % 100, "%02d",
363353358Sdim							pt, ptlim);
364353358Sdim					} else	pt = _conv(year, "%04d",
365353358Sdim							pt, ptlim);
366360784Sdim				}
367360784Sdim				continue;
368254721Semaste			case 'v':
369353358Sdim				/*
370353358Sdim				** From Arnold Robbins' strftime version 3.0:
371353358Sdim				** "date as dd-bbb-YYYY"
372353358Sdim				** (ado, 5/24/93)
373353358Sdim				*/
374353358Sdim				pt = _fmt("%e-%b-%Y", t, pt, ptlim);
375353358Sdim				continue;
376353358Sdim			case 'W':
377360784Sdim				pt = _conv((t->tm_yday + 7 -
378360784Sdim					(t->tm_wday ?
379353358Sdim					(t->tm_wday - 1) : 6)) / 7,
380353358Sdim					"%02d", pt, ptlim);
381360784Sdim				continue;
382353358Sdim			case 'w':
383353358Sdim				pt = _conv(t->tm_wday, "%d", pt, ptlim);
384353358Sdim				continue;
385353358Sdim			case 'X':
386353358Sdim				pt = _fmt(tptr->X_fmt, t, pt, ptlim);
387353358Sdim				continue;
388353358Sdim			case 'x':
389353358Sdim				pt = _fmt(tptr->x_fmt, t, pt, ptlim);
390353358Sdim				continue;
391360784Sdim			case 'y':
392360784Sdim				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
393360784Sdim					"%02d", pt, ptlim);
394360784Sdim				continue;
395353358Sdim			case 'Y':
396353358Sdim				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
397353358Sdim					pt, ptlim);
398353358Sdim				continue;
399353358Sdim			case 'Z':
400353358Sdim				if (t->tm_zone != NULL)
401353358Sdim					pt = _add(t->tm_zone, pt, ptlim);
402353358Sdim				else
403353358Sdim				if (t->tm_isdst == 0 || t->tm_isdst == 1) {
404353358Sdim					pt = _add(tzname[t->tm_isdst],
405353358Sdim						pt, ptlim);
406353358Sdim				} else  pt = _add("?", pt, ptlim);
407353358Sdim				continue;
408353358Sdim			case 'z':
409353358Sdim				{
410353358Sdim					long absoff;
411353358Sdim					if (t->tm_gmtoff >= 0) {
412353358Sdim						absoff = t->tm_gmtoff;
413360784Sdim						pt = _add("+", pt, ptlim);
414360784Sdim					} else {
415360784Sdim						absoff = -t->tm_gmtoff;
416360784Sdim						pt = _add("-", pt, ptlim);
417360784Sdim					}
418360784Sdim					pt = _conv(absoff / 3600, "%02d",
419360784Sdim						pt, ptlim);
420360784Sdim					pt = _conv((absoff % 3600) / 60, "%02d",
421353358Sdim						pt, ptlim);
422353358Sdim				};
423353358Sdim				continue;
424353358Sdim			case '+':
425353358Sdim				pt = _fmt(tptr->date_fmt, t, pt, ptlim);
426353358Sdim				continue;
427353358Sdim			case '%':
428353358Sdim			/*
429353358Sdim			 * X311J/88-090 (4.12.3.5): if conversion char is
430353358Sdim			 * undefined, behavior is undefined.  Print out the
431353358Sdim			 * character itself as printf(3) also does.
432353358Sdim			 */
433353358Sdim			default:
434353358Sdim				break;
435353358Sdim			}
436		}
437		if (pt == ptlim)
438			break;
439		*pt++ = *format;
440	}
441	return pt;
442}
443
444static char *
445_conv(n, format, pt, ptlim)
446	const int n;
447	const char *const format;
448	char *const pt;
449	const char *const ptlim;
450{
451	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
452
453	(void) sprintf(buf, format, n);
454	return _add(buf, pt, ptlim);
455}
456
457static char *
458_add(str, pt, ptlim)
459	const char *str;
460	char *pt;
461	const char *const ptlim;
462{
463	while (pt < ptlim && (*pt = *str++) != '\0')
464		++pt;
465	return pt;
466}
467