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