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