strftime.c revision 6815
1#ifndef lint
2#ifndef NOID
3static char	elsieid[] = "@(#)strftime.c	7.19";
4/*
5** Based on the UCB version with the ID appearing below.
6** This is ANSIish only when time is treated identically in all locales and
7** when "multibyte character == plain character".
8*/
9#endif /* !defined NOID */
10#endif /* !defined lint */
11
12#include "private.h"
13
14/*
15 * Copyright (c) 1989 The Regents of the University of California.
16 * All rights reserved.
17 *
18 * Redistribution and use in source and binary forms are permitted
19 * provided that the above copyright notice and this paragraph are
20 * duplicated in all such forms and that any documentation,
21 * advertising materials, and other materials related to such
22 * distribution and use acknowledge that the software was developed
23 * by the University of California, Berkeley.  The name of the
24 * University may not be used to endorse or promote products derived
25 * from this software without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
27 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 */
30
31#ifndef LIBC_SCCS
32#ifndef lint
33static const char sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
34#endif /* !defined lint */
35#endif /* !defined LIBC_SCCS */
36
37#include "tzfile.h"
38
39static const char afmt[][4] = {
40	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
41};
42static const char Afmt[][10] = {
43	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
44	"Saturday"
45};
46static const char bfmt[][4] = {
47	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
48	"Oct", "Nov", "Dec"
49};
50static const char Bfmt[][10] = {
51	"January", "February", "March", "April", "May", "June", "July",
52	"August", "September", "October", "November", "December"
53};
54
55static char *_add P((const char *, char *, const char *));
56static char *_conv P((int, const char *, char *, const char *));
57static char *_fmt P((const char *, const struct tm *, char *, const char *));
58static char *_secs P((const struct tm *, char *, const char *));
59
60size_t strftime P((char *, size_t, const char *, const struct tm *));
61
62extern char *tzname[];
63
64size_t
65strftime(s, maxsize, format, t)
66	char *s;
67	size_t maxsize;
68	const char *format;
69	const struct tm *t;
70{
71	char *p;
72
73	p = _fmt(format, t, s, s + maxsize);
74	if (p == s + maxsize)
75		return 0;
76	*p = '\0';
77	return p - s;
78}
79
80static char *
81_fmt(format, t, pt, ptlim)
82	const char *format;
83	const struct tm *t;
84	char *pt;
85	const char *ptlim;
86{
87	for (; *format; ++format) {
88		if (*format == '%') {
89label:
90			switch(*++format) {
91			case '\0':
92				--format;
93				break;
94			case 'A':
95				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
96					"?" : Afmt[t->tm_wday], pt, ptlim);
97				continue;
98			case 'a':
99				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
100					"?" : afmt[t->tm_wday], pt, ptlim);
101				continue;
102			case 'B':
103				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
104					"?" : Bfmt[t->tm_mon], pt, ptlim);
105				continue;
106			case 'b':
107			case 'h':
108				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
109					"?" : bfmt[t->tm_mon], pt, ptlim);
110				continue;
111			case 'c':
112				pt = _fmt("%D %X", t, 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 'D':
126				pt = _fmt("%m/%d/%y", t, pt, ptlim);
127				continue;
128			case 'x':
129				/*
130				** Version 3.0 of strftime from Arnold Robbins
131				** (arnold@skeeve.atl.ga.us) does the
132				** equivalent of...
133				**	_fmt("%a %b %e %Y");
134				** ...for %x; since the X3J11 C language
135				** standard calls for "date, using locale's
136				** date format," anything goes.  Using just
137				** numbers (as here) makes Quakers happier.
138				** Word from Paul Eggert (eggert@twinsun.com)
139				** is that %Y-%m-%d is the ISO standard date
140				** format, specified in ISO 2014 and later
141				** ISO 8601:1988, with a summary available in
142				** pub/doc/ISO/english/ISO8601.ps.Z on
143				** ftp.uni-erlangen.de.
144				** (ado, 5/30/93)
145				*/
146				pt = _fmt("%m/%d/%y", t, pt, ptlim);
147				continue;
148			case 'd':
149				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
150				continue;
151			case 'E':
152			case 'O':
153				/*
154				** POSIX locale extensions, a la
155				** Arnold Robbins' strftime version 3.0.
156				** The sequences
157				**	%Ec %EC %Ex %Ey %EY
158				**	%Od %oe %OH %OI %Om %OM
159				**	%OS %Ou %OU %OV %Ow %OW %Oy
160				** are supposed to provide alternate
161				** representations.
162				** (ado, 5/24/93)
163				*/
164				goto label;
165			case 'e':
166				pt = _conv(t->tm_mday, "%2d", 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 ? "PM" : "AM",
225					pt, ptlim);
226				continue;
227			case 'R':
228				pt = _fmt("%H:%M", t, pt, ptlim);
229				continue;
230			case 'r':
231				pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
232				continue;
233			case 'S':
234				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
235				continue;
236			case 's':
237				pt = _secs(t, pt, ptlim);
238				continue;
239			case 'T':
240			case 'X':
241				pt = _fmt("%H:%M:%S", t, pt, ptlim);
242				continue;
243			case 't':
244				pt = _add("\t", pt, ptlim);
245				continue;
246			case 'U':
247				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
248					"%02d", pt, ptlim);
249				continue;
250			case 'u':
251				/*
252				** From Arnold Robbins' strftime version 3.0:
253				** "ISO 8601: Weekday as a decimal number
254				** [1 (Monday) - 7]"
255				** (ado, 5/24/93)
256				*/
257				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
258					"%d", pt, ptlim);
259				continue;
260			case 'V':
261				/*
262				** From Arnold Robbins' strftime version 3.0:
263				** "the week number of the year (the first
264				** Monday as the first day of week 1) as a
265				** decimal number (01-53).  The method for
266				** determining the week number is as specified
267				** by ISO 8601 (to wit: if the week containing
268				** January 1 has four or more days in the new
269				** year, then it is week 1, otherwise it is
270				** week 53 of the previous year and the next
271				** week is week 1)."
272				** (ado, 5/24/93)
273				*/
274				/*
275				** XXX--If January 1 falls on a Friday,
276				** January 1-3 are part of week 53 of the
277				** previous year.  By analogy, if January
278				** 1 falls on a Thursday, are December 29-31
279				** of the PREVIOUS year part of week 1???
280				** (ado 5/24/93)
281				**
282				** You are understood not to expect this.
283				*/
284				{
285					int i;
286
287					i = (t->tm_yday + 10 - (t->tm_wday ?
288						(t->tm_wday - 1) : 6)) / 7;
289					if (i == 0) {
290						/*
291						** What day of the week does
292						** January 1 fall on?
293						*/
294						i = t->tm_wday -
295							(t->tm_yday - 1);
296						/*
297						** Fri Jan 1: 53
298						** Sun Jan 1: 52
299						** Sat Jan 1: 53 if previous
300						** 		 year a leap
301						**		 year, else 52
302						*/
303						if (i == TM_FRIDAY)
304							i = 53;
305						else if (i == TM_SUNDAY)
306							i = 52;
307						else	i = isleap(t->tm_year +
308								TM_YEAR_BASE) ?
309								53 : 52;
310#ifdef XPG4_1994_04_09
311						/*
312						** As of 4/9/94, though,
313						** XPG4 calls for 53
314						** unconditionally.
315						*/
316						i = 53;
317#endif /* defined XPG4_1994_04_09 */
318					}
319					pt = _conv(i, "%02d", pt, ptlim);
320				}
321				continue;
322			case 'v':
323				/*
324				** From Arnold Robbins' strftime version 3.0:
325				** "date as dd-bbb-YYYY"
326				** (ado, 5/24/93)
327				*/
328				pt = _fmt("%e-%b-%Y", t, pt, ptlim);
329				continue;
330			case 'W':
331				pt = _conv((t->tm_yday + 7 -
332					(t->tm_wday ?
333					(t->tm_wday - 1) : 6)) / 7,
334					"%02d", pt, ptlim);
335				continue;
336			case 'w':
337				pt = _conv(t->tm_wday, "%d", pt, ptlim);
338				continue;
339			case 'y':
340				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
341					"%02d", pt, ptlim);
342				continue;
343			case 'Y':
344				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
345					pt, ptlim);
346				continue;
347			case 'Z':
348#ifdef TM_ZONE
349				if (t->TM_ZONE)
350					pt = _add(t->TM_ZONE, pt, ptlim);
351				else
352#endif /* defined TM_ZONE */
353				if (t->tm_isdst == 0 || t->tm_isdst == 1) {
354					pt = _add(tzname[t->tm_isdst],
355						pt, ptlim);
356				} else  pt = _add("?", pt, ptlim);
357				continue;
358			case '%':
359			/*
360			 * X311J/88-090 (4.12.3.5): if conversion char is
361			 * undefined, behavior is undefined.  Print out the
362			 * character itself as printf(3) also does.
363			 */
364			default:
365				break;
366			}
367		}
368		if (pt == ptlim)
369			break;
370		*pt++ = *format;
371	}
372	return pt;
373}
374
375static char *
376_conv(n, format, pt, ptlim)
377	int n;
378	const char *format;
379	char *pt;
380	const char *ptlim;
381{
382	char buf[INT_STRLEN_MAXIMUM(int) + 1];
383
384	(void) sprintf(buf, format, n);
385	return _add(buf, pt, ptlim);
386}
387
388static char *
389_secs(t, pt, ptlim)
390	const struct tm *t;
391	char *pt;
392	const char *ptlim;
393{
394	static char buf[INT_STRLEN_MAXIMUM(int) + 1];
395	register time_t s;
396	register char *p;
397	struct tm tmp;
398
399	/* Make a copy, mktime(3) modifies the tm struct. */
400	tmp = *t;
401	s = mktime(&tmp);
402	(void) sprintf(buf, "%d", s);
403	return(_add(buf, pt, ptlim));
404}
405
406static char *
407_add(str, pt, ptlim)
408	const char *str;
409	char *pt;
410	const char *ptlim;
411{
412	while (pt < ptlim && (*pt = *str++) != '\0')
413		++pt;
414	return pt;
415}
416