xref: /freebsd/lib/libc/stdtime/strftime.c (revision df7f5d4de4592a8948a25ce01e5bddfbb7ce39dc)
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
19 static const char rcsid[] =
20 	"$Id$";
21 #endif
22 
23 #ifndef lint
24 #ifndef NOID
25 static 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 "private.h"
34 
35 #ifndef LIBC_SCCS
36 #ifndef lint
37 static const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
38 #endif /* !defined lint */
39 #endif /* !defined LIBC_SCCS */
40 
41 #include "tzfile.h"
42 #include <fcntl.h>
43 #include <locale.h>
44 #include <sys/stat.h>
45 #include "setlocale.h"
46 
47 struct lc_time_T {
48 	const char *	mon[12];
49 	const char *	month[12];
50 	const char *	wday[7];
51 	const char *	weekday[7];
52 	const char *	X_fmt;
53 	const char *	x_fmt;
54 	const char *	c_fmt;
55 	const char *	am;
56 	const char *	pm;
57 	const char *	date_fmt;
58 };
59 
60 static struct lc_time_T		localebuf;
61 static int using_locale;
62 
63 #define Locale	(using_locale ? &localebuf : &C_time_locale)
64 
65 static const struct lc_time_T	C_time_locale = {
66 	{
67 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
68 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
69 	}, {
70 		"January", "February", "March", "April", "May", "June",
71 		"July", "August", "September", "October", "November", "December"
72 	}, {
73 		"Sun", "Mon", "Tue", "Wed",
74 		"Thu", "Fri", "Sat"
75 	}, {
76 		"Sunday", "Monday", "Tuesday", "Wednesday",
77 		"Thursday", "Friday", "Saturday"
78 	},
79 
80 	/* X_fmt */
81 	"%H:%M:%S",
82 
83 	/*
84 	** x_fmt
85 	** Since the C language standard calls for
86 	** "date, using locale's date format," anything goes.
87 	** Using just numbers (as here) makes Quakers happier;
88 	** it's also compatible with SVR4.
89 	*/
90 	"%m/%d/%y",
91 
92 	/*
93 	** c_fmt (ctime-compatible)
94 	** Note that
95 	**	"%a %b %d %H:%M:%S %Y"
96 	** is used by Solaris 2.3.
97 	*/
98 	"%a %b %e %X %Y",
99 
100 	/* am */
101 	"AM",
102 
103 	/* pm */
104 	"PM",
105 
106 	/* date_fmt */
107 	"%a %b %e %X %Z %Y"
108 };
109 
110 static char *	_add P((const char *, char *, const char *));
111 static char *	_conv P((int, const char *, char *, const char *));
112 static char *	_fmt P((const char *, const struct tm *, char *, const char *));
113 static char *   _secs P((const struct tm *, char *, const char *));
114 
115 size_t strftime P((char *, size_t, const char *, const struct tm *));
116 
117 extern char *	tzname[];
118 
119 size_t
120 strftime(s, maxsize, format, t)
121 	char *const s;
122 	const size_t maxsize;
123 	const char *const format;
124 	const struct tm *const t;
125 {
126 	char *p;
127 
128 	tzset();
129 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
130 	if (p == s + maxsize)
131 		return 0;
132 	*p = '\0';
133 	return p - s;
134 }
135 
136 static char *
137 _fmt(format, t, pt, ptlim)
138 	const char *format;
139 	const struct tm *const t;
140 	char *pt;
141 	const char *const ptlim;
142 {
143 	for ( ; *format; ++format) {
144 		if (*format == '%') {
145 label:
146 			switch (*++format) {
147 			case '\0':
148 				--format;
149 				break;
150 			case 'A':
151 				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
152 					"?" : Locale->weekday[t->tm_wday],
153 					pt, ptlim);
154 				continue;
155 			case 'a':
156 				pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
157 					"?" : Locale->wday[t->tm_wday],
158 					pt, ptlim);
159 				continue;
160 			case 'B':
161 				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
162 					"?" : Locale->month[t->tm_mon],
163 					pt, ptlim);
164 				continue;
165 			case 'b':
166 			case 'h':
167 				pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
168 					"?" : Locale->mon[t->tm_mon],
169 					pt, ptlim);
170 				continue;
171 			case 'C':
172 				/*
173 				** %C used to do a...
174 				**	_fmt("%a %b %e %X %Y", t);
175 				** ...whereas now POSIX 1003.2 calls for
176 				** something completely different.
177 				** (ado, 5/24/93)
178 				*/
179 				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
180 					"%02d", pt, ptlim);
181 				continue;
182 			case 'c':
183 				pt = _fmt(Locale->c_fmt, t, pt, ptlim);
184 				continue;
185 			case 'D':
186 				pt = _fmt("%m/%d/%y", t, pt, ptlim);
187 				continue;
188 			case 'd':
189 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
190 				continue;
191 			case 'E':
192 			case 'O':
193 				/*
194 				** POSIX locale extensions, a la
195 				** Arnold Robbins' strftime version 3.0.
196 				** The sequences
197 				**	%Ec %EC %Ex %Ey %EY
198 				**	%Od %oe %OH %OI %Om %OM
199 				**	%OS %Ou %OU %OV %Ow %OW %Oy
200 				** are supposed to provide alternate
201 				** representations.
202 				** (ado, 5/24/93)
203 				*/
204 				goto label;
205 			case 'e':
206 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
207 				continue;
208 			case 'H':
209 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
210 				continue;
211 			case 'I':
212 				pt = _conv((t->tm_hour % 12) ?
213 					(t->tm_hour % 12) : 12,
214 					"%02d", pt, ptlim);
215 				continue;
216 			case 'j':
217 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
218 				continue;
219 			case 'k':
220 				/*
221 				** This used to be...
222 				**	_conv(t->tm_hour % 12 ?
223 				**		t->tm_hour % 12 : 12, 2, ' ');
224 				** ...and has been changed to the below to
225 				** match SunOS 4.1.1 and Arnold Robbins'
226 				** strftime version 3.0.  That is, "%k" and
227 				** "%l" have been swapped.
228 				** (ado, 5/24/93)
229 				*/
230 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
231 				continue;
232 #ifdef KITCHEN_SINK
233 			case 'K':
234 				/*
235 				** After all this time, still unclaimed!
236 				*/
237 				pt = _add("kitchen sink", pt, ptlim);
238 				continue;
239 #endif /* defined KITCHEN_SINK */
240 			case 'l':
241 				/*
242 				** This used to be...
243 				**	_conv(t->tm_hour, 2, ' ');
244 				** ...and has been changed to the below to
245 				** match SunOS 4.1.1 and Arnold Robbin's
246 				** strftime version 3.0.  That is, "%k" and
247 				** "%l" have been swapped.
248 				** (ado, 5/24/93)
249 				*/
250 				pt = _conv((t->tm_hour % 12) ?
251 					(t->tm_hour % 12) : 12,
252 					"%2d", pt, ptlim);
253 				continue;
254 			case 'M':
255 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
256 				continue;
257 			case 'm':
258 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
259 				continue;
260 			case 'n':
261 				pt = _add("\n", pt, ptlim);
262 				continue;
263 			case 'p':
264 				pt = _add((t->tm_hour >= 12) ?
265 					Locale->pm :
266 					Locale->am,
267 					pt, ptlim);
268 				continue;
269 			case 'R':
270 				pt = _fmt("%H:%M", t, pt, ptlim);
271 				continue;
272 			case 'r':
273 				pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
274 				continue;
275 			case 'S':
276 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
277 				continue;
278 			case 's':
279 				pt = _secs(t, pt, ptlim);
280 				continue;
281 			case 'T':
282 				pt = _fmt("%H:%M:%S", t, pt, ptlim);
283 				continue;
284 			case 't':
285 				pt = _add("\t", pt, ptlim);
286 				continue;
287 			case 'U':
288 				pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
289 					"%02d", pt, ptlim);
290 				continue;
291 			case 'u':
292 				/*
293 				** From Arnold Robbins' strftime version 3.0:
294 				** "ISO 8601: Weekday as a decimal number
295 				** [1 (Monday) - 7]"
296 				** (ado, 5/24/93)
297 				*/
298 				pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
299 					"%d", pt, ptlim);
300 				continue;
301 			case 'V':
302 				/*
303 				** From Arnold Robbins' strftime version 3.0:
304 				** "the week number of the year (the first
305 				** Monday as the first day of week 1) as a
306 				** decimal number (01-53).  The method for
307 				** determining the week number is as specified
308 				** by ISO 8601 (to wit: if the week containing
309 				** January 1 has four or more days in the new
310 				** year, then it is week 1, otherwise it is
311 				** week 53 of the previous year and the next
312 				** week is week 1)."
313 				** (ado, 5/24/93)
314 				*/
315 				/*
316 				** XXX--If January 1 falls on a Friday,
317 				** January 1-3 are part of week 53 of the
318 				** previous year.  By analogy, if January
319 				** 1 falls on a Thursday, are December 29-31
320 				** of the PREVIOUS year part of week 1???
321 				** (ado 5/24/93)
322 				*/
323 				/*
324 				** You are understood not to expect this.
325 				*/
326 				{
327 					int	i;
328 
329 					i = (t->tm_yday + 10 - (t->tm_wday ?
330 						(t->tm_wday - 1) : 6)) / 7;
331 					if (i == 0) {
332 						/*
333 						** What day of the week does
334 						** January 1 fall on?
335 						*/
336 						i = t->tm_wday -
337 							(t->tm_yday - 1);
338 						/*
339 						** Fri Jan 1: 53
340 						** Sun Jan 1: 52
341 						** Sat Jan 1: 53 if previous
342 						**		 year a leap
343 						**		 year, else 52
344 						*/
345 						if (i == TM_FRIDAY)
346 							i = 53;
347 						else if (i == TM_SUNDAY)
348 							i = 52;
349 						else	i = isleap(t->tm_year +
350 								TM_YEAR_BASE) ?
351 								53 : 52;
352 #ifdef XPG4_1994_04_09
353 						/*
354 						** As of 4/9/94, though,
355 						** XPG4 calls for 53
356 						** unconditionally.
357 						*/
358 						i = 53;
359 #endif /* defined XPG4_1994_04_09 */
360 					}
361 					pt = _conv(i, "%02d", 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(Locale->X_fmt, t, pt, ptlim);
383 				continue;
384 			case 'x':
385 				pt = _fmt(Locale->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 '+':
405 				pt = _fmt(Locale->date_fmt, t, pt, ptlim);
406 				continue;
407 			case '%':
408 			/*
409 			 * X311J/88-090 (4.12.3.5): if conversion char is
410 			 * undefined, behavior is undefined.  Print out the
411 			 * character itself as printf(3) also does.
412 			 */
413 			default:
414 				break;
415 			}
416 		}
417 		if (pt == ptlim)
418 			break;
419 		*pt++ = *format;
420 	}
421 	return pt;
422 }
423 
424 static char *
425 _conv(n, format, pt, ptlim)
426 	const int n;
427 	const char *const format;
428 	char *const pt;
429 	const char *const ptlim;
430 {
431 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
432 
433 	(void) sprintf(buf, format, n);
434 	return _add(buf, pt, ptlim);
435 }
436 
437 static char *
438 _secs(t, pt, ptlim)
439 	const struct tm *t;
440 	char *pt;
441 	const char *ptlim;
442 {
443 	char    buf[INT_STRLEN_MAXIMUM(int) + 1];
444 	register time_t s;
445 	struct tm tmp;
446 
447 	/* Make a copy, mktime(3) modifies the tm struct. */
448 	tmp = *t;
449 	s = mktime(&tmp);
450 	(void) sprintf(buf, "%ld", s);
451 	return _add(buf, pt, ptlim);
452 }
453 
454 static char *
455 _add(str, pt, ptlim)
456 	const char *str;
457 	char *pt;
458 	const char *const ptlim;
459 {
460 	while (pt < ptlim && (*pt = *str++) != '\0')
461 		++pt;
462 	return pt;
463 }
464 
465 int
466 __time_load_locale(const char *name)
467 {
468 	static char *		locale_buf;
469 	static char		locale_buf_C[] = "C";
470 
471 	int			fd;
472 	char *			lbuf;
473 	char *			p;
474 	const char **		ap;
475 	const char *		plim;
476 	char                    filename[PATH_MAX];
477 	struct stat		st;
478 	size_t			namesize;
479 	size_t			bufsize;
480 	int                     save_using_locale;
481 
482 	save_using_locale = using_locale;
483 	using_locale = 0;
484 
485 	if (name == NULL)
486 		goto no_locale;
487 
488 	if (!strcmp(name, "C") || !strcmp(name, "POSIX"))
489 		return 0;
490 
491 	/*
492 	** If the locale name is the same as our cache, use the cache.
493 	*/
494 	lbuf = locale_buf;
495 	if (lbuf != NULL && strcmp(name, lbuf) == 0) {
496 		p = lbuf;
497 		for (ap = (const char **) &localebuf;
498 			ap < (const char **) (&localebuf + 1);
499 				++ap)
500 					*ap = p += strlen(p) + 1;
501 		using_locale = 1;
502 		return 0;
503 	}
504 	/*
505 	** Slurp the locale file into the cache.
506 	*/
507 	namesize = strlen(name) + 1;
508 
509 	if (!_PathLocale)
510 		goto no_locale;
511 	/* Range checking not needed, 'name' size is limited */
512 	strcpy(filename, _PathLocale);
513 	strcat(filename, "/");
514 	strcat(filename, name);
515 	strcat(filename, "/LC_TIME");
516 	fd = open(filename, O_RDONLY);
517 	if (fd < 0)
518 		goto no_locale;
519 	if (fstat(fd, &st) != 0)
520 		goto bad_locale;
521 	if (st.st_size <= 0)
522 		goto bad_locale;
523 	bufsize = namesize + st.st_size;
524 	locale_buf = NULL;
525 	lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
526 		malloc(bufsize) : realloc(lbuf, bufsize);
527 	if (lbuf == NULL)
528 		goto bad_locale;
529 	(void) strcpy(lbuf, name);
530 	p = lbuf + namesize;
531 	plim = p + st.st_size;
532 	if (read(fd, p, (size_t) st.st_size) != st.st_size)
533 		goto bad_lbuf;
534 	if (close(fd) != 0)
535 		goto bad_lbuf;
536 	/*
537 	** Parse the locale file into localebuf.
538 	*/
539 	if (plim[-1] != '\n')
540 		goto bad_lbuf;
541 	for (ap = (const char **) &localebuf;
542 		ap < (const char **) (&localebuf + 1);
543 			++ap) {
544 				if (p == plim)
545 					goto reset_locale;
546 				*ap = p;
547 				while (*p != '\n')
548 					++p;
549 				*p++ = '\0';
550 	}
551 	/*
552 	** Record the successful parse in the cache.
553 	*/
554 	locale_buf = lbuf;
555 
556 	using_locale = 1;
557 	return 0;
558 
559 reset_locale:
560 	/*
561 	 * XXX - This may not be the correct thing to do in this case.
562 	 * setlocale() assumes that we left the old locale alone.
563 	 */
564 	locale_buf = locale_buf_C;
565 	localebuf = C_time_locale;
566 	save_using_locale = 0;
567 bad_lbuf:
568 	free(lbuf);
569 bad_locale:
570 	(void) close(fd);
571 no_locale:
572 	using_locale = save_using_locale;
573 	return -1;
574 }
575