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