xref: /freebsd/lib/libc/stdtime/strftime.c (revision a35d88931c87cfe6bd38f01d7bad22140b3b38f3)
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.64";
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 *, int *);
46 
47 size_t strftime(char * __restrict, size_t, const char * __restrict,
48     const struct tm * __restrict);
49 
50 extern char *	tzname[];
51 
52 #ifndef YEAR_2000_NAME
53 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
54 #endif /* !defined YEAR_2000_NAME */
55 
56 
57 #define IN_NONE	0
58 #define IN_SOME	1
59 #define IN_THIS	2
60 #define IN_ALL	3
61 
62 size_t
63 strftime(char * __restrict s, size_t maxsize, const char * __restrict format,
64     const struct tm * __restrict t)
65 {
66 	char *	p;
67 	int	warn;
68 
69 	tzset();
70 	warn = IN_NONE;
71 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
72 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
73 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
74 		(void) fprintf(stderr, "\n");
75 		if (format == NULL)
76 			(void) fprintf(stderr, "NULL strftime format ");
77 		else	(void) fprintf(stderr, "strftime format \"%s\" ",
78 				format);
79 		(void) fprintf(stderr, "yields only two digits of years in ");
80 		if (warn == IN_SOME)
81 			(void) fprintf(stderr, "some locales");
82 		else if (warn == IN_THIS)
83 			(void) fprintf(stderr, "the current locale");
84 		else	(void) fprintf(stderr, "all locales");
85 		(void) fprintf(stderr, "\n");
86 	}
87 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
88 	if (p == s + maxsize)
89 		return 0;
90 	*p = '\0';
91 	return p - s;
92 }
93 
94 static char *
95 _fmt(format, t, pt, ptlim, warnp)
96 const char *		format;
97 const struct tm * const	t;
98 char *			pt;
99 const char * const	ptlim;
100 int *			warnp;
101 {
102 	int Ealternative, Oalternative;
103 	struct lc_time_T *tptr = __get_current_time_locale();
104 
105 	for ( ; *format; ++format) {
106 		if (*format == '%') {
107 			Ealternative = 0;
108 			Oalternative = 0;
109 label:
110 			switch (*++format) {
111 			case '\0':
112 				--format;
113 				break;
114 			case 'A':
115 				pt = _add((t->tm_wday < 0 ||
116 					t->tm_wday >= DAYSPERWEEK) ?
117 					"?" : tptr->weekday[t->tm_wday],
118 					pt, ptlim);
119 				continue;
120 			case 'a':
121 				pt = _add((t->tm_wday < 0 ||
122 					t->tm_wday >= DAYSPERWEEK) ?
123 					"?" : tptr->wday[t->tm_wday],
124 					pt, ptlim);
125 				continue;
126 			case 'B':
127 				pt = _add((t->tm_mon < 0 ||
128 					t->tm_mon >= MONSPERYEAR) ?
129 					"?" : (Oalternative ? tptr->alt_month :
130 					tptr->month)[t->tm_mon],
131 					pt, ptlim);
132 				continue;
133 			case 'b':
134 			case 'h':
135 				pt = _add((t->tm_mon < 0 ||
136 					t->tm_mon >= MONSPERYEAR) ?
137 					"?" : tptr->mon[t->tm_mon],
138 					pt, ptlim);
139 				continue;
140 			case 'C':
141 				/*
142 				** %C used to do a...
143 				**	_fmt("%a %b %e %X %Y", t);
144 				** ...whereas now POSIX 1003.2 calls for
145 				** something completely different.
146 				** (ado, 1993-05-24)
147 				*/
148 				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
149 					"%02d", pt, ptlim);
150 				continue;
151 			case 'c':
152 				{
153 				int warn2 = IN_SOME;
154 
155 				pt = _fmt(tptr->c_fmt, t, pt, ptlim, warnp);
156 				if (warn2 == IN_ALL)
157 					warn2 = IN_THIS;
158 				if (warn2 > *warnp)
159 					*warnp = warn2;
160 				}
161 				continue;
162 			case 'D':
163 				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
164 				continue;
165 			case 'd':
166 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
167 				continue;
168 			case 'E':
169 				if (Ealternative || Oalternative)
170 					break;
171 				Ealternative++;
172 				goto label;
173 			case 'O':
174 				/*
175 				** C99 locale modifiers.
176 				** The sequences
177 				**	%Ec %EC %Ex %EX %Ey %EY
178 				**	%Od %oe %OH %OI %Om %OM
179 				**	%OS %Ou %OU %OV %Ow %OW %Oy
180 				** are supposed to provide alternate
181 				** representations.
182 				**
183 				** FreeBSD extension
184 				**      %OB
185 				*/
186 				if (Ealternative || Oalternative)
187 					break;
188 				Oalternative++;
189 				goto label;
190 			case 'e':
191 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
192 				continue;
193 			case 'F':
194 				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
195 				continue;
196 			case 'H':
197 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
198 				continue;
199 			case 'I':
200 				pt = _conv((t->tm_hour % 12) ?
201 					(t->tm_hour % 12) : 12,
202 					"%02d", pt, ptlim);
203 				continue;
204 			case 'j':
205 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
206 				continue;
207 			case 'k':
208 				/*
209 				** This used to be...
210 				**	_conv(t->tm_hour % 12 ?
211 				**		t->tm_hour % 12 : 12, 2, ' ');
212 				** ...and has been changed to the below to
213 				** match SunOS 4.1.1 and Arnold Robbins'
214 				** strftime version 3.0.  That is, "%k" and
215 				** "%l" have been swapped.
216 				** (ado, 1993-05-24)
217 				*/
218 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
219 				continue;
220 #ifdef KITCHEN_SINK
221 			case 'K':
222 				/*
223 				** After all this time, still unclaimed!
224 				*/
225 				pt = _add("kitchen sink", pt, ptlim);
226 				continue;
227 #endif /* defined KITCHEN_SINK */
228 			case 'l':
229 				/*
230 				** This used to be...
231 				**	_conv(t->tm_hour, 2, ' ');
232 				** ...and has been changed to the below to
233 				** match SunOS 4.1.1 and Arnold Robbin's
234 				** strftime version 3.0.  That is, "%k" and
235 				** "%l" have been swapped.
236 				** (ado, 1993-05-24)
237 				*/
238 				pt = _conv((t->tm_hour % 12) ?
239 					(t->tm_hour % 12) : 12,
240 					"%2d", pt, ptlim);
241 				continue;
242 			case 'M':
243 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
244 				continue;
245 			case 'm':
246 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
247 				continue;
248 			case 'n':
249 				pt = _add("\n", pt, ptlim);
250 				continue;
251 			case 'p':
252 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
253 					tptr->pm :
254 					tptr->am,
255 					pt, ptlim);
256 				continue;
257 			case 'R':
258 				pt = _fmt("%H:%M", t, pt, ptlim, warnp);
259 				continue;
260 			case 'r':
261 				pt = _fmt(tptr->ampm_fmt, t, pt, ptlim,
262 					warnp);
263 				continue;
264 			case 'S':
265 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
266 				continue;
267 			case 's':
268 				{
269 					struct tm	tm;
270 					char		buf[INT_STRLEN_MAXIMUM(
271 								time_t) + 1];
272 					time_t		mkt;
273 
274 					tm = *t;
275 					mkt = mktime(&tm);
276 					if (TYPE_SIGNED(time_t))
277 						(void) sprintf(buf, "%ld",
278 							(long) mkt);
279 					else	(void) sprintf(buf, "%lu",
280 							(unsigned long) mkt);
281 					pt = _add(buf, pt, ptlim);
282 				}
283 				continue;
284 			case 'T':
285 				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
286 				continue;
287 			case 't':
288 				pt = _add("\t", pt, ptlim);
289 				continue;
290 			case 'U':
291 				pt = _conv((t->tm_yday + DAYSPERWEEK -
292 					t->tm_wday) / DAYSPERWEEK,
293 					"%02d", pt, ptlim);
294 				continue;
295 			case 'u':
296 				/*
297 				** From Arnold Robbins' strftime version 3.0:
298 				** "ISO 8601: Weekday as a decimal number
299 				** [1 (Monday) - 7]"
300 				** (ado, 1993-05-24)
301 				*/
302 				pt = _conv((t->tm_wday == 0) ?
303 					DAYSPERWEEK : t->tm_wday,
304 					"%d", pt, ptlim);
305 				continue;
306 			case 'V':	/* ISO 8601 week number */
307 			case 'G':	/* ISO 8601 year (four digits) */
308 			case 'g':	/* ISO 8601 year (two digits) */
309 /*
310 ** From Arnold Robbins' strftime version 3.0:  "the week number of the
311 ** year (the first Monday as the first day of week 1) as a decimal number
312 ** (01-53)."
313 ** (ado, 1993-05-24)
314 **
315 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
316 ** "Week 01 of a year is per definition the first week which has the
317 ** Thursday in this year, which is equivalent to the week which contains
318 ** the fourth day of January. In other words, the first week of a new year
319 ** is the week which has the majority of its days in the new year. Week 01
320 ** might also contain days from the previous year and the week before week
321 ** 01 of a year is the last week (52 or 53) of the previous year even if
322 ** it contains days from the new year. A week starts with Monday (day 1)
323 ** and ends with Sunday (day 7).  For example, the first week of the year
324 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
325 ** (ado, 1996-01-02)
326 */
327 				{
328 					int	year;
329 					int	yday;
330 					int	wday;
331 					int	w;
332 
333 					year = t->tm_year + TM_YEAR_BASE;
334 					yday = t->tm_yday;
335 					wday = t->tm_wday;
336 					for ( ; ; ) {
337 						int	len;
338 						int	bot;
339 						int	top;
340 
341 						len = isleap(year) ?
342 							DAYSPERLYEAR :
343 							DAYSPERNYEAR;
344 						/*
345 						** What yday (-3 ... 3) does
346 						** the ISO year begin on?
347 						*/
348 						bot = ((yday + 11 - wday) %
349 							DAYSPERWEEK) - 3;
350 						/*
351 						** What yday does the NEXT
352 						** ISO year begin on?
353 						*/
354 						top = bot -
355 							(len % DAYSPERWEEK);
356 						if (top < -3)
357 							top += DAYSPERWEEK;
358 						top += len;
359 						if (yday >= top) {
360 							++year;
361 							w = 1;
362 							break;
363 						}
364 						if (yday >= bot) {
365 							w = 1 + ((yday - bot) /
366 								DAYSPERWEEK);
367 							break;
368 						}
369 						--year;
370 						yday += isleap(year) ?
371 							DAYSPERLYEAR :
372 							DAYSPERNYEAR;
373 					}
374 #ifdef XPG4_1994_04_09
375 					if ((w == 52
376 					     && t->tm_mon == TM_JANUARY)
377 					    || (w == 1
378 						&& t->tm_mon == TM_DECEMBER))
379 						w = 53;
380 #endif /* defined XPG4_1994_04_09 */
381 					if (*format == 'V')
382 						pt = _conv(w, "%02d",
383 							pt, ptlim);
384 					else if (*format == 'g') {
385 						*warnp = IN_ALL;
386 						pt = _conv(year % 100, "%02d",
387 							pt, ptlim);
388 					} else	pt = _conv(year, "%04d",
389 							pt, ptlim);
390 				}
391 				continue;
392 			case 'v':
393 				/*
394 				** From Arnold Robbins' strftime version 3.0:
395 				** "date as dd-bbb-YYYY"
396 				** (ado, 1993-05-24)
397 				*/
398 				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
399 				continue;
400 			case 'W':
401 				pt = _conv((t->tm_yday + DAYSPERWEEK -
402 					(t->tm_wday ?
403 					(t->tm_wday - 1) :
404 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
405 					"%02d", pt, ptlim);
406 				continue;
407 			case 'w':
408 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
409 				continue;
410 			case 'X':
411 				pt = _fmt(tptr->X_fmt, t, pt, ptlim, warnp);
412 				continue;
413 			case 'x':
414 				{
415 				int	warn2 = IN_SOME;
416 
417 				pt = _fmt(tptr->x_fmt, t, pt, ptlim, &warn2);
418 				if (warn2 == IN_ALL)
419 					warn2 = IN_THIS;
420 				if (warn2 > *warnp)
421 					*warnp = warn2;
422 				}
423 				continue;
424 			case 'y':
425 				*warnp = IN_ALL;
426 				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
427 					"%02d", pt, ptlim);
428 				continue;
429 			case 'Y':
430 				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
431 					pt, ptlim);
432 				continue;
433 			case 'Z':
434 #ifdef TM_ZONE
435 				if (t->TM_ZONE != NULL)
436 					pt = _add(t->TM_ZONE, pt, ptlim);
437 				else
438 #endif /* defined TM_ZONE */
439 				if (t->tm_isdst >= 0)
440 					pt = _add(tzname[t->tm_isdst != 0],
441 						pt, ptlim);
442 				/*
443 				** C99 says that %Z must be replaced by the
444 				** empty string if the time zone is not
445 				** determinable.
446 				*/
447 				continue;
448 			case 'z':
449 				{
450 				int		diff;
451 				char const *	sign;
452 
453 				if (t->tm_isdst < 0)
454 					continue;
455 #ifdef TM_GMTOFF
456 				diff = t->TM_GMTOFF;
457 #else /* !defined TM_GMTOFF */
458 				/*
459 				** C99 says that the UTC offset must
460 				** be computed by looking only at
461 				** tm_isdst.  This requirement is
462 				** incorrect, since it means the code
463 				** must rely on magic (in this case
464 				** altzone and timezone), and the
465 				** magic might not have the correct
466 				** offset.  Doing things correctly is
467 				** tricky and requires disobeying C99;
468 				** see GNU C strftime for details.
469 				** For now, punt and conform to the
470 				** standard, even though it's incorrect.
471 				**
472 				** C99 says that %z must be replaced by the
473 				** empty string if the time zone is not
474 				** determinable, so output nothing if the
475 				** appropriate variables are not available.
476 				*/
477 				if (t->tm_isdst == 0)
478 #ifdef USG_COMPAT
479 					diff = -timezone;
480 #else /* !defined USG_COMPAT */
481 					continue;
482 #endif /* !defined USG_COMPAT */
483 				else
484 #ifdef ALTZONE
485 					diff = -altzone;
486 #else /* !defined ALTZONE */
487 					continue;
488 #endif /* !defined ALTZONE */
489 #endif /* !defined TM_GMTOFF */
490 				if (diff < 0) {
491 					sign = "-";
492 					diff = -diff;
493 				} else	sign = "+";
494 				pt = _add(sign, pt, ptlim);
495 				diff /= 60;
496 				pt = _conv((diff/60)*100 + diff%60,
497 					"%04d", pt, ptlim);
498 				}
499 				continue;
500 			case '+':
501 				pt = _fmt(tptr->date_fmt, t, pt, ptlim,
502 					warnp);
503 				continue;
504 			case '%':
505 			/*
506 			** X311J/88-090 (4.12.3.5): if conversion char is
507 			** undefined, behavior is undefined.  Print out the
508 			** character itself as printf(3) also does.
509 			*/
510 			default:
511 				break;
512 			}
513 		}
514 		if (pt == ptlim)
515 			break;
516 		*pt++ = *format;
517 	}
518 	return pt;
519 }
520 
521 static char *
522 _conv(n, format, pt, ptlim)
523 const int		n;
524 const char * const	format;
525 char * const		pt;
526 const char * const	ptlim;
527 {
528 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
529 
530 	(void) sprintf(buf, format, n);
531 	return _add(buf, pt, ptlim);
532 }
533 
534 static char *
535 _add(str, pt, ptlim)
536 const char *		str;
537 char *			pt;
538 const char * const	ptlim;
539 {
540 	while (pt < ptlim && (*pt = *str++) != '\0')
541 		++pt;
542 	return pt;
543 }
544