xref: /openbsd-src/lib/libc/time/strftime.c (revision a37af58cfc9a6102ae50efe6821b3db382dd8f5d)
1*a37af58cSmillert /*	$OpenBSD: strftime.c,v 1.33 2020/07/16 20:08:12 millert Exp $ */
2efdf7294Smillert /*
35e4f8c55Smillert ** Copyright (c) 1989, 1993
45e4f8c55Smillert **	The Regents of the University of California.  All rights reserved.
5efdf7294Smillert **
65e4f8c55Smillert ** Redistribution and use in source and binary forms, with or without
75e4f8c55Smillert ** modification, are permitted provided that the following conditions
85e4f8c55Smillert ** are met:
95e4f8c55Smillert ** 1. Redistributions of source code must retain the above copyright
105e4f8c55Smillert **    notice, this list of conditions and the following disclaimer.
115e4f8c55Smillert ** 2. Redistributions in binary form must reproduce the above copyright
125e4f8c55Smillert **    notice, this list of conditions and the following disclaimer in the
135e4f8c55Smillert **    documentation and/or other materials provided with the distribution.
14bed2b053Sderaadt ** 3. Neither the name of the University nor the names of its contributors
155e4f8c55Smillert **    may be used to endorse or promote products derived from this software
165e4f8c55Smillert **    without specific prior written permission.
175e4f8c55Smillert **
185e4f8c55Smillert ** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
195e4f8c55Smillert ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
205e4f8c55Smillert ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
215e4f8c55Smillert ** ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
225e4f8c55Smillert ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
235e4f8c55Smillert ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
245e4f8c55Smillert ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
255e4f8c55Smillert ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
265e4f8c55Smillert ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
275e4f8c55Smillert ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
285e4f8c55Smillert ** SUCH DAMAGE.
29efdf7294Smillert */
30efdf7294Smillert 
31a9cc4792Stedu #include <fcntl.h>
32dbd4bd29Sguenther #include <stdio.h>
33a9cc4792Stedu 
34a9cc4792Stedu #include "private.h"
35efdf7294Smillert #include "tzfile.h"
36efdf7294Smillert 
37efdf7294Smillert struct lc_time_T {
38efdf7294Smillert 	const char *	mon[MONSPERYEAR];
39efdf7294Smillert 	const char *	month[MONSPERYEAR];
40efdf7294Smillert 	const char *	wday[DAYSPERWEEK];
41efdf7294Smillert 	const char *	weekday[DAYSPERWEEK];
42efdf7294Smillert 	const char *	X_fmt;
43efdf7294Smillert 	const char *	x_fmt;
44efdf7294Smillert 	const char *	c_fmt;
45efdf7294Smillert 	const char *	am;
46efdf7294Smillert 	const char *	pm;
47efdf7294Smillert 	const char *	date_fmt;
48efdf7294Smillert };
49efdf7294Smillert 
50efdf7294Smillert #define Locale	(&C_time_locale)
51efdf7294Smillert static const struct lc_time_T	C_time_locale = {
52efdf7294Smillert 	{
53efdf7294Smillert 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
54efdf7294Smillert 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
55efdf7294Smillert 	}, {
56efdf7294Smillert 		"January", "February", "March", "April", "May", "June",
57efdf7294Smillert 		"July", "August", "September", "October", "November", "December"
58efdf7294Smillert 	}, {
59efdf7294Smillert 		"Sun", "Mon", "Tue", "Wed",
60efdf7294Smillert 		"Thu", "Fri", "Sat"
61efdf7294Smillert 	}, {
62efdf7294Smillert 		"Sunday", "Monday", "Tuesday", "Wednesday",
63efdf7294Smillert 		"Thursday", "Friday", "Saturday"
64efdf7294Smillert 	},
65efdf7294Smillert 
66efdf7294Smillert 	/* X_fmt */
67efdf7294Smillert 	"%H:%M:%S",
68efdf7294Smillert 
69efdf7294Smillert 	/*
70efdf7294Smillert 	** x_fmt
71169f132fSd 	** C99 requires this format.
72efdf7294Smillert 	** Using just numbers (as here) makes Quakers happier;
73efdf7294Smillert 	** it's also compatible with SVR4.
74efdf7294Smillert 	*/
75efdf7294Smillert 	"%m/%d/%y",
76efdf7294Smillert 
77efdf7294Smillert 	/*
78efdf7294Smillert 	** c_fmt
79169f132fSd 	** C99 requires this format.
80169f132fSd 	** Previously this code used "%D %X", but we now conform to C99.
81169f132fSd 	** Note that
82169f132fSd 	**	"%a %b %d %H:%M:%S %Y"
83169f132fSd 	** is used by Solaris 2.3.
84efdf7294Smillert 	*/
85169f132fSd 	"%a %b %e %T %Y",
86efdf7294Smillert 
87efdf7294Smillert 	/* am */
88efdf7294Smillert 	"AM",
89efdf7294Smillert 
90efdf7294Smillert 	/* pm */
91efdf7294Smillert 	"PM",
92efdf7294Smillert 
93efdf7294Smillert 	/* date_fmt */
94efdf7294Smillert 	"%a %b %e %H:%M:%S %Z %Y"
95efdf7294Smillert };
96efdf7294Smillert 
97f896db16Smillert static char *	_add(const char *, char *, const char *);
98f896db16Smillert static char *	_conv(int, const char *, char *, const char *);
99f896db16Smillert static char *	_fmt(const char *, const struct tm *, char *, const char *,
100f896db16Smillert 			int *);
101f896db16Smillert static char *	_yconv(int, int, int, int, char *, const char *);
102efdf7294Smillert 
103efdf7294Smillert extern char *	tzname[];
104efdf7294Smillert 
105efdf7294Smillert #define IN_NONE	0
106efdf7294Smillert #define IN_SOME	1
107efdf7294Smillert #define IN_THIS	2
108efdf7294Smillert #define IN_ALL	3
109efdf7294Smillert 
110efdf7294Smillert size_t
strftime(char * s,size_t maxsize,const char * format,const struct tm * t)1118dc2b201Stedu strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
112efdf7294Smillert {
113efdf7294Smillert 	char *	p;
114efdf7294Smillert 	int	warn;
115efdf7294Smillert 
116efdf7294Smillert 	tzset();
117efdf7294Smillert 	warn = IN_NONE;
118efdf7294Smillert 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
11922bc1b62Smillert 	if (p == s + maxsize) {
120d6bae9b0Smillert 		if (maxsize > 0)
12122bc1b62Smillert 			s[maxsize - 1] = '\0';
122efdf7294Smillert 		return 0;
12322bc1b62Smillert 	}
124efdf7294Smillert 	*p = '\0';
125efdf7294Smillert 	return p - s;
126efdf7294Smillert }
127d17652bcSguenther DEF_STRONG(strftime);
128efdf7294Smillert 
129efdf7294Smillert static char *
_fmt(const char * format,const struct tm * t,char * pt,const char * ptlim,int * warnp)1308dc2b201Stedu _fmt(const char *format, const struct tm *t, char *pt, const char *ptlim, int *warnp)
131efdf7294Smillert {
132efdf7294Smillert 	for ( ; *format; ++format) {
133efdf7294Smillert 		if (*format == '%') {
134efdf7294Smillert label:
135efdf7294Smillert 			switch (*++format) {
136efdf7294Smillert 			case '\0':
137efdf7294Smillert 				--format;
138efdf7294Smillert 				break;
139efdf7294Smillert 			case 'A':
140efdf7294Smillert 				pt = _add((t->tm_wday < 0 ||
141efdf7294Smillert 					t->tm_wday >= DAYSPERWEEK) ?
142efdf7294Smillert 					"?" : Locale->weekday[t->tm_wday],
143efdf7294Smillert 					pt, ptlim);
144efdf7294Smillert 				continue;
145efdf7294Smillert 			case 'a':
146efdf7294Smillert 				pt = _add((t->tm_wday < 0 ||
147efdf7294Smillert 					t->tm_wday >= DAYSPERWEEK) ?
148efdf7294Smillert 					"?" : Locale->wday[t->tm_wday],
149efdf7294Smillert 					pt, ptlim);
150efdf7294Smillert 				continue;
151efdf7294Smillert 			case 'B':
152efdf7294Smillert 				pt = _add((t->tm_mon < 0 ||
153efdf7294Smillert 					t->tm_mon >= MONSPERYEAR) ?
154efdf7294Smillert 					"?" : Locale->month[t->tm_mon],
155efdf7294Smillert 					pt, ptlim);
156efdf7294Smillert 				continue;
157efdf7294Smillert 			case 'b':
158efdf7294Smillert 			case 'h':
159efdf7294Smillert 				pt = _add((t->tm_mon < 0 ||
160efdf7294Smillert 					t->tm_mon >= MONSPERYEAR) ?
161efdf7294Smillert 					"?" : Locale->mon[t->tm_mon],
162efdf7294Smillert 					pt, ptlim);
163efdf7294Smillert 				continue;
164efdf7294Smillert 			case 'C':
165efdf7294Smillert 				/*
166efdf7294Smillert 				** %C used to do a...
167efdf7294Smillert 				**	_fmt("%a %b %e %X %Y", t);
168efdf7294Smillert 				** ...whereas now POSIX 1003.2 calls for
169efdf7294Smillert 				** something completely different.
170efdf7294Smillert 				** (ado, 1993-05-24)
171efdf7294Smillert 				*/
1723f56448eSmillert 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
1733f56448eSmillert 					pt, ptlim);
174efdf7294Smillert 				continue;
175efdf7294Smillert 			case 'c':
176efdf7294Smillert 				{
177efdf7294Smillert 				int warn2 = IN_SOME;
178efdf7294Smillert 
1790cd7f77fSmillert 				pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
180efdf7294Smillert 				if (warn2 == IN_ALL)
181efdf7294Smillert 					warn2 = IN_THIS;
182efdf7294Smillert 				if (warn2 > *warnp)
183efdf7294Smillert 					*warnp = warn2;
184efdf7294Smillert 				}
185efdf7294Smillert 				continue;
186efdf7294Smillert 			case 'D':
187efdf7294Smillert 				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
188efdf7294Smillert 				continue;
189efdf7294Smillert 			case 'd':
190efdf7294Smillert 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
191efdf7294Smillert 				continue;
192efdf7294Smillert 			case 'E':
193efdf7294Smillert 			case 'O':
194efdf7294Smillert 				/*
195ef12f6f8Smillert 				** C99 locale modifiers.
196efdf7294Smillert 				** The sequences
197ef12f6f8Smillert 				**	%Ec %EC %Ex %EX %Ey %EY
198efdf7294Smillert 				**	%Od %oe %OH %OI %Om %OM
199efdf7294Smillert 				**	%OS %Ou %OU %OV %Ow %OW %Oy
200efdf7294Smillert 				** are supposed to provide alternate
201efdf7294Smillert 				** representations.
202efdf7294Smillert 				*/
203efdf7294Smillert 				goto label;
204efdf7294Smillert 			case 'e':
205efdf7294Smillert 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
206efdf7294Smillert 				continue;
207ef12f6f8Smillert 			case 'F':
208ef12f6f8Smillert 				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
209ef12f6f8Smillert 				continue;
210efdf7294Smillert 			case 'H':
211efdf7294Smillert 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
212efdf7294Smillert 				continue;
213efdf7294Smillert 			case 'I':
214efdf7294Smillert 				pt = _conv((t->tm_hour % 12) ?
215efdf7294Smillert 					(t->tm_hour % 12) : 12,
216efdf7294Smillert 					"%02d", pt, ptlim);
217efdf7294Smillert 				continue;
218efdf7294Smillert 			case 'j':
219efdf7294Smillert 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
220efdf7294Smillert 				continue;
221efdf7294Smillert 			case 'k':
222efdf7294Smillert 				/*
223efdf7294Smillert 				** This used to be...
224efdf7294Smillert 				**	_conv(t->tm_hour % 12 ?
225efdf7294Smillert 				**		t->tm_hour % 12 : 12, 2, ' ');
226efdf7294Smillert 				** ...and has been changed to the below to
227efdf7294Smillert 				** match SunOS 4.1.1 and Arnold Robbins'
228efdf7294Smillert 				** strftime version 3.0. That is, "%k" and
229efdf7294Smillert 				** "%l" have been swapped.
230efdf7294Smillert 				** (ado, 1993-05-24)
231efdf7294Smillert 				*/
232efdf7294Smillert 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
233efdf7294Smillert 				continue;
234efdf7294Smillert #ifdef KITCHEN_SINK
235efdf7294Smillert 			case 'K':
236efdf7294Smillert 				/*
237efdf7294Smillert 				** After all this time, still unclaimed!
238efdf7294Smillert 				*/
239efdf7294Smillert 				pt = _add("kitchen sink", pt, ptlim);
240efdf7294Smillert 				continue;
241efdf7294Smillert #endif /* defined KITCHEN_SINK */
242efdf7294Smillert 			case 'l':
243efdf7294Smillert 				/*
244efdf7294Smillert 				** This used to be...
245efdf7294Smillert 				**	_conv(t->tm_hour, 2, ' ');
246efdf7294Smillert 				** ...and has been changed to the below to
247efdf7294Smillert 				** match SunOS 4.1.1 and Arnold Robbin's
248efdf7294Smillert 				** strftime version 3.0. That is, "%k" and
249efdf7294Smillert 				** "%l" have been swapped.
250efdf7294Smillert 				** (ado, 1993-05-24)
251efdf7294Smillert 				*/
252efdf7294Smillert 				pt = _conv((t->tm_hour % 12) ?
253efdf7294Smillert 					(t->tm_hour % 12) : 12,
254efdf7294Smillert 					"%2d", pt, ptlim);
255efdf7294Smillert 				continue;
256efdf7294Smillert 			case 'M':
257efdf7294Smillert 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
258efdf7294Smillert 				continue;
259efdf7294Smillert 			case 'm':
260efdf7294Smillert 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
261efdf7294Smillert 				continue;
262efdf7294Smillert 			case 'n':
263efdf7294Smillert 				pt = _add("\n", pt, ptlim);
264efdf7294Smillert 				continue;
265efdf7294Smillert 			case 'p':
266efdf7294Smillert 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
267efdf7294Smillert 					Locale->pm :
268efdf7294Smillert 					Locale->am,
269efdf7294Smillert 					pt, ptlim);
270efdf7294Smillert 				continue;
271efdf7294Smillert 			case 'R':
272efdf7294Smillert 				pt = _fmt("%H:%M", t, pt, ptlim, warnp);
273efdf7294Smillert 				continue;
274efdf7294Smillert 			case 'r':
275efdf7294Smillert 				pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
276efdf7294Smillert 				continue;
277efdf7294Smillert 			case 'S':
278efdf7294Smillert 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
279efdf7294Smillert 				continue;
280efdf7294Smillert 			case 's':
281efdf7294Smillert 				{
282efdf7294Smillert 					struct tm	tm;
283efdf7294Smillert 					char		buf[INT_STRLEN_MAXIMUM(
284efdf7294Smillert 								time_t) + 1];
285efdf7294Smillert 					time_t		mkt;
286efdf7294Smillert 
287efdf7294Smillert 					tm = *t;
288efdf7294Smillert 					mkt = mktime(&tm);
289c364cbbdSderaadt 					(void) snprintf(buf, sizeof buf,
290c364cbbdSderaadt 					    "%ld", (long) mkt);
291efdf7294Smillert 					pt = _add(buf, pt, ptlim);
292efdf7294Smillert 				}
293efdf7294Smillert 				continue;
294efdf7294Smillert 			case 'T':
295efdf7294Smillert 				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
296efdf7294Smillert 				continue;
297efdf7294Smillert 			case 't':
298efdf7294Smillert 				pt = _add("\t", pt, ptlim);
299efdf7294Smillert 				continue;
300efdf7294Smillert 			case 'U':
301efdf7294Smillert 				pt = _conv((t->tm_yday + DAYSPERWEEK -
302efdf7294Smillert 					t->tm_wday) / DAYSPERWEEK,
303efdf7294Smillert 					"%02d", pt, ptlim);
304efdf7294Smillert 				continue;
305efdf7294Smillert 			case 'u':
306efdf7294Smillert 				/*
307efdf7294Smillert 				** From Arnold Robbins' strftime version 3.0:
308efdf7294Smillert 				** "ISO 8601: Weekday as a decimal number
309efdf7294Smillert 				** [1 (Monday) - 7]"
310efdf7294Smillert 				** (ado, 1993-05-24)
311efdf7294Smillert 				*/
312efdf7294Smillert 				pt = _conv((t->tm_wday == 0) ?
313efdf7294Smillert 					DAYSPERWEEK : t->tm_wday,
314efdf7294Smillert 					"%d", pt, ptlim);
315efdf7294Smillert 				continue;
316efdf7294Smillert 			case 'V':	/* ISO 8601 week number */
317efdf7294Smillert 			case 'G':	/* ISO 8601 year (four digits) */
318efdf7294Smillert 			case 'g':	/* ISO 8601 year (two digits) */
319efdf7294Smillert /*
320efdf7294Smillert ** From Arnold Robbins' strftime version 3.0: "the week number of the
321efdf7294Smillert ** year (the first Monday as the first day of week 1) as a decimal number
322efdf7294Smillert ** (01-53)."
323efdf7294Smillert ** (ado, 1993-05-24)
324efdf7294Smillert **
325efdf7294Smillert ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
326efdf7294Smillert ** "Week 01 of a year is per definition the first week which has the
327efdf7294Smillert ** Thursday in this year, which is equivalent to the week which contains
328efdf7294Smillert ** the fourth day of January. In other words, the first week of a new year
329efdf7294Smillert ** is the week which has the majority of its days in the new year. Week 01
330efdf7294Smillert ** might also contain days from the previous year and the week before week
331efdf7294Smillert ** 01 of a year is the last week (52 or 53) of the previous year even if
332efdf7294Smillert ** it contains days from the new year. A week starts with Monday (day 1)
333efdf7294Smillert ** and ends with Sunday (day 7). For example, the first week of the year
334efdf7294Smillert ** 1997 lasts from 1996-12-30 to 1997-01-05..."
335efdf7294Smillert ** (ado, 1996-01-02)
336efdf7294Smillert */
337efdf7294Smillert 				{
3383f56448eSmillert 					int	year;
3393f56448eSmillert 					int	base;
340efdf7294Smillert 					int	yday;
341efdf7294Smillert 					int	wday;
342efdf7294Smillert 					int	w;
343efdf7294Smillert 
344dfd58e64Smillert 					year = t->tm_year;
3453f56448eSmillert 					base = TM_YEAR_BASE;
346efdf7294Smillert 					yday = t->tm_yday;
347efdf7294Smillert 					wday = t->tm_wday;
348efdf7294Smillert 					for ( ; ; ) {
349efdf7294Smillert 						int	len;
350efdf7294Smillert 						int	bot;
351efdf7294Smillert 						int	top;
352efdf7294Smillert 
3533f56448eSmillert 						len = isleap_sum(year, base) ?
354efdf7294Smillert 							DAYSPERLYEAR :
355efdf7294Smillert 							DAYSPERNYEAR;
356efdf7294Smillert 						/*
357efdf7294Smillert 						** What yday (-3 ... 3) does
358efdf7294Smillert 						** the ISO year begin on?
359efdf7294Smillert 						*/
360efdf7294Smillert 						bot = ((yday + 11 - wday) %
361efdf7294Smillert 							DAYSPERWEEK) - 3;
362efdf7294Smillert 						/*
363efdf7294Smillert 						** What yday does the NEXT
364efdf7294Smillert 						** ISO year begin on?
365efdf7294Smillert 						*/
366efdf7294Smillert 						top = bot -
367efdf7294Smillert 							(len % DAYSPERWEEK);
368efdf7294Smillert 						if (top < -3)
369efdf7294Smillert 							top += DAYSPERWEEK;
370efdf7294Smillert 						top += len;
371efdf7294Smillert 						if (yday >= top) {
3723f56448eSmillert 							++base;
373efdf7294Smillert 							w = 1;
374efdf7294Smillert 							break;
375efdf7294Smillert 						}
376efdf7294Smillert 						if (yday >= bot) {
377efdf7294Smillert 							w = 1 + ((yday - bot) /
378efdf7294Smillert 								DAYSPERWEEK);
379efdf7294Smillert 							break;
380efdf7294Smillert 						}
3813f56448eSmillert 						--base;
3823f56448eSmillert 						yday += isleap_sum(year, base) ?
383efdf7294Smillert 							DAYSPERLYEAR :
384efdf7294Smillert 							DAYSPERNYEAR;
385efdf7294Smillert 					}
386efdf7294Smillert #ifdef XPG4_1994_04_09
387dfd58e64Smillert 					if ((w == 52 &&
388dfd58e64Smillert 						t->tm_mon == TM_JANUARY) ||
389dfd58e64Smillert 						(w == 1 &&
390dfd58e64Smillert 						t->tm_mon == TM_DECEMBER))
391efdf7294Smillert 							w = 53;
392efdf7294Smillert #endif /* defined XPG4_1994_04_09 */
393efdf7294Smillert 					if (*format == 'V')
394efdf7294Smillert 						pt = _conv(w, "%02d",
395efdf7294Smillert 							pt, ptlim);
396efdf7294Smillert 					else if (*format == 'g') {
397efdf7294Smillert 						*warnp = IN_ALL;
3983f56448eSmillert 						pt = _yconv(year, base, 0, 1,
3993f56448eSmillert 							pt, ptlim);
4003f56448eSmillert 					} else	pt = _yconv(year, base, 1, 1,
401efdf7294Smillert 							pt, ptlim);
402efdf7294Smillert 				}
403efdf7294Smillert 				continue;
404efdf7294Smillert 			case 'v':
405efdf7294Smillert 				/*
406efdf7294Smillert 				** From Arnold Robbins' strftime version 3.0:
407efdf7294Smillert 				** "date as dd-bbb-YYYY"
408efdf7294Smillert 				** (ado, 1993-05-24)
409efdf7294Smillert 				*/
410efdf7294Smillert 				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
411efdf7294Smillert 				continue;
412efdf7294Smillert 			case 'W':
413efdf7294Smillert 				pt = _conv((t->tm_yday + DAYSPERWEEK -
414efdf7294Smillert 					(t->tm_wday ?
415efdf7294Smillert 					(t->tm_wday - 1) :
416efdf7294Smillert 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
417efdf7294Smillert 					"%02d", pt, ptlim);
418efdf7294Smillert 				continue;
419efdf7294Smillert 			case 'w':
420efdf7294Smillert 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
421efdf7294Smillert 				continue;
422efdf7294Smillert 			case 'X':
423efdf7294Smillert 				pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
424efdf7294Smillert 				continue;
425efdf7294Smillert 			case 'x':
426efdf7294Smillert 				{
427efdf7294Smillert 				int	warn2 = IN_SOME;
428efdf7294Smillert 
429efdf7294Smillert 				pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
430efdf7294Smillert 				if (warn2 == IN_ALL)
431efdf7294Smillert 					warn2 = IN_THIS;
432efdf7294Smillert 				if (warn2 > *warnp)
433efdf7294Smillert 					*warnp = warn2;
434efdf7294Smillert 				}
435efdf7294Smillert 				continue;
436efdf7294Smillert 			case 'y':
437efdf7294Smillert 				*warnp = IN_ALL;
4383f56448eSmillert 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
4393f56448eSmillert 					pt, ptlim);
440efdf7294Smillert 				continue;
441efdf7294Smillert 			case 'Y':
4423f56448eSmillert 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
4433f56448eSmillert 					pt, ptlim);
444efdf7294Smillert 				continue;
445efdf7294Smillert 			case 'Z':
446bb5f5481Sschwarze 				if (t->tm_zone != NULL)
447bb5f5481Sschwarze 					pt = _add(t->tm_zone, pt, ptlim);
448bb5f5481Sschwarze 				else if (t->tm_isdst >= 0)
449ef12f6f8Smillert 					pt = _add(tzname[t->tm_isdst != 0],
450efdf7294Smillert 						pt, ptlim);
451ef12f6f8Smillert 				/*
452ef12f6f8Smillert 				** C99 says that %Z must be replaced by the
453ef12f6f8Smillert 				** empty string if the time zone is not
454ef12f6f8Smillert 				** determinable.
455ef12f6f8Smillert 				*/
456ef12f6f8Smillert 				continue;
457ef12f6f8Smillert 			case 'z':
458ef12f6f8Smillert 				{
459ef12f6f8Smillert 				int		diff;
460ef12f6f8Smillert 				char const *	sign;
461ef12f6f8Smillert 
462ef12f6f8Smillert 				if (t->tm_isdst < 0)
463ef12f6f8Smillert 					continue;
464bb5f5481Sschwarze 				diff = t->tm_gmtoff;
465ef12f6f8Smillert 				if (diff < 0) {
466ef12f6f8Smillert 					sign = "-";
467ef12f6f8Smillert 					diff = -diff;
468ef12f6f8Smillert 				} else	sign = "+";
469ef12f6f8Smillert 				pt = _add(sign, pt, ptlim);
4703f56448eSmillert 				diff /= SECSPERMIN;
4713f56448eSmillert 				diff = (diff / MINSPERHOUR) * 100 +
4723f56448eSmillert 					(diff % MINSPERHOUR);
4733f56448eSmillert 				pt = _conv(diff, "%04d", pt, ptlim);
474ef12f6f8Smillert 				}
475efdf7294Smillert 				continue;
476efdf7294Smillert 			case '+':
477efdf7294Smillert 				pt = _fmt(Locale->date_fmt, t, pt, ptlim,
478efdf7294Smillert 					warnp);
479efdf7294Smillert 				continue;
480efdf7294Smillert 			case '%':
481efdf7294Smillert 			/*
482ef12f6f8Smillert 			** X311J/88-090 (4.12.3.5): if conversion char is
483ef12f6f8Smillert 			** undefined, behavior is undefined. Print out the
484ef12f6f8Smillert 			** character itself as printf(3) also does.
485efdf7294Smillert 			*/
486efdf7294Smillert 			default:
487efdf7294Smillert 				break;
488efdf7294Smillert 			}
489efdf7294Smillert 		}
490efdf7294Smillert 		if (pt == ptlim)
491efdf7294Smillert 			break;
492efdf7294Smillert 		*pt++ = *format;
493efdf7294Smillert 	}
494efdf7294Smillert 	return pt;
495efdf7294Smillert }
496efdf7294Smillert 
497efdf7294Smillert static char *
_conv(int n,const char * format,char * pt,const char * ptlim)4988dc2b201Stedu _conv(int n, const char *format, char *pt, const char *ptlim)
499efdf7294Smillert {
500efdf7294Smillert 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
501efdf7294Smillert 
502c364cbbdSderaadt 	(void) snprintf(buf, sizeof buf, format, n);
503efdf7294Smillert 	return _add(buf, pt, ptlim);
504efdf7294Smillert }
505efdf7294Smillert 
506efdf7294Smillert static char *
_add(const char * str,char * pt,const char * ptlim)5078dc2b201Stedu _add(const char *str, char *pt, const char *ptlim)
508efdf7294Smillert {
509efdf7294Smillert 	while (pt < ptlim && (*pt = *str++) != '\0')
510efdf7294Smillert 		++pt;
511efdf7294Smillert 	return pt;
512efdf7294Smillert }
513efdf7294Smillert 
5143f56448eSmillert /*
5153f56448eSmillert ** POSIX and the C Standard are unclear or inconsistent about
5163f56448eSmillert ** what %C and %y do if the year is negative or exceeds 9999.
5173f56448eSmillert ** Use the convention that %C concatenated with %y yields the
5183f56448eSmillert ** same output as %Y, and that %Y contains at least 4 bytes,
5193f56448eSmillert ** with more only if necessary.
5203f56448eSmillert */
5213f56448eSmillert 
5223f56448eSmillert static char *
_yconv(int a,int b,int convert_top,int convert_yy,char * pt,const char * ptlim)5238dc2b201Stedu _yconv(int a, int b, int convert_top, int convert_yy, char *pt, const char *ptlim)
5243f56448eSmillert {
5256a6b658aStedu 	int	lead;
5266a6b658aStedu 	int	trail;
5273f56448eSmillert 
5283f56448eSmillert #define DIVISOR	100
5293f56448eSmillert 	trail = a % DIVISOR + b % DIVISOR;
5303f56448eSmillert 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
5313f56448eSmillert 	trail %= DIVISOR;
5323f56448eSmillert 	if (trail < 0 && lead > 0) {
5333f56448eSmillert 		trail += DIVISOR;
5343f56448eSmillert 		--lead;
5353f56448eSmillert 	} else if (lead < 0 && trail > 0) {
5363f56448eSmillert 		trail -= DIVISOR;
5373f56448eSmillert 		++lead;
5383f56448eSmillert 	}
5393f56448eSmillert 	if (convert_top) {
5403f56448eSmillert 		if (lead == 0 && trail < 0)
5413f56448eSmillert 			pt = _add("-0", pt, ptlim);
5423f56448eSmillert 		else	pt = _conv(lead, "%02d", pt, ptlim);
5433f56448eSmillert 	}
5443f56448eSmillert 	if (convert_yy)
5453f56448eSmillert 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
5463f56448eSmillert 	return pt;
5473f56448eSmillert }
548