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