xref: /netbsd/lib/libc/time/strftime.c (revision c4a72b64)
1 /*	$NetBSD: strftime.c,v 1.14 2002/10/02 06:56:24 itojun Exp $	*/
2 
3 #include <sys/cdefs.h>
4 #if defined(LIBC_SCCS) && !defined(lint)
5 #if 0
6 static char	elsieid[] = "@(#)strftime.c	7.64";
7 #else
8 __RCSID("$NetBSD: strftime.c,v 1.14 2002/10/02 06:56:24 itojun Exp $");
9 #endif
10 #endif /* LIBC_SCCS and not lint */
11 
12 #include "namespace.h"
13 
14 /*
15 ** Based on the UCB version with the ID appearing below.
16 ** This is ANSIish only when "multibyte character == plain character".
17 */
18 
19 #include "private.h"
20 
21 /*
22 ** Copyright (c) 1989, 1993
23 **	The Regents of the University of California.  All rights reserved.
24 **
25 ** Redistribution and use in source and binary forms, with or without
26 ** modification, are permitted provided that the following conditions
27 ** are met:
28 ** 1. Redistributions of source code must retain the above copyright
29 **    notice, this list of conditions and the following disclaimer.
30 ** 2. Redistributions in binary form must reproduce the above copyright
31 **    notice, this list of conditions and the following disclaimer in the
32 **    documentation and/or other materials provided with the distribution.
33 ** 3. All advertising materials mentioning features or use of this software
34 **    must display the following acknowledgement:
35 **	This product includes software developed by the University of
36 **	California, Berkeley and its contributors.
37 ** 4. Neither the name of the University nor the names of its contributors
38 **    may be used to endorse or promote products derived from this software
39 **    without specific prior written permission.
40 **
41 ** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
42 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
44 ** ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
45 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
46 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
47 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
49 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
50 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51 ** SUCH DAMAGE.
52 */
53 
54 #ifndef LIBC_SCCS
55 #ifndef lint
56 static const char	sccsid[] = "@(#)strftime.c	5.4 (Berkeley) 3/14/89";
57 #endif /* !defined lint */
58 #endif /* !defined LIBC_SCCS */
59 
60 #include "tzfile.h"
61 #include "fcntl.h"
62 #include "locale.h"
63 
64 #include "sys/localedef.h"
65 #define Locale	_CurrentTimeLocale
66 
67 static char *	_add P((const char *, char *, const char *));
68 static char *	_conv P((int, const char *, char *, const char *));
69 static char *	_fmt P((const char *, const struct tm *, char *, const char *, int *));
70 
71 #define NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
72 
73 #ifndef YEAR_2000_NAME
74 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
75 #endif /* !defined YEAR_2000_NAME */
76 
77 
78 #define IN_NONE	0
79 #define IN_SOME	1
80 #define IN_THIS	2
81 #define IN_ALL	3
82 
83 size_t
84 strftime(s, maxsize, format, t)
85 char * const		s;
86 const size_t		maxsize;
87 const char * const	format;
88 const struct tm * const	t;
89 {
90 	char *	p;
91 	int	warn;
92 
93 	tzset();
94 	warn = IN_NONE;
95 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
96 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
97 	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
98 		(void) fprintf(stderr, "\n");
99 		if (format == NULL)
100 			(void) fprintf(stderr, "NULL strftime format ");
101 		else	(void) fprintf(stderr, "strftime format \"%s\" ",
102 				format);
103 		(void) fprintf(stderr, "yields only two digits of years in ");
104 		if (warn == IN_SOME)
105 			(void) fprintf(stderr, "some locales");
106 		else if (warn == IN_THIS)
107 			(void) fprintf(stderr, "the current locale");
108 		else	(void) fprintf(stderr, "all locales");
109 		(void) fprintf(stderr, "\n");
110 	}
111 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
112 	if (p == s + maxsize)
113 		return 0;
114 	*p = '\0';
115 	return p - s;
116 }
117 
118 static char *
119 _fmt(format, t, pt, ptlim, warnp)
120 const char *		format;
121 const struct tm * const	t;
122 char *			pt;
123 const char * const	ptlim;
124 int *			warnp;
125 {
126 	for ( ; *format; ++format) {
127 		if (*format == '%') {
128 label:
129 			switch (*++format) {
130 			case '\0':
131 				--format;
132 				break;
133 			case 'A':
134 				pt = _add((t->tm_wday < 0 ||
135 					t->tm_wday >= DAYSPERWEEK) ?
136 					"?" : Locale->day[t->tm_wday],
137 					pt, ptlim);
138 				continue;
139 			case 'a':
140 				pt = _add((t->tm_wday < 0 ||
141 					t->tm_wday >= DAYSPERWEEK) ?
142 					"?" : Locale->abday[t->tm_wday],
143 					pt, ptlim);
144 				continue;
145 			case 'B':
146 				pt = _add((t->tm_mon < 0 ||
147 					t->tm_mon >= MONSPERYEAR) ?
148 					"?" : Locale->mon[t->tm_mon],
149 					pt, ptlim);
150 				continue;
151 			case 'b':
152 			case 'h':
153 				pt = _add((t->tm_mon < 0 ||
154 					t->tm_mon >= MONSPERYEAR) ?
155 					"?" : Locale->abmon[t->tm_mon],
156 					pt, ptlim);
157 				continue;
158 			case 'C':
159 				/*
160 				** %C used to do a...
161 				**	_fmt("%a %b %e %X %Y", t);
162 				** ...whereas now POSIX 1003.2 calls for
163 				** something completely different.
164 				** (ado, 1993-05-24)
165 				*/
166 				pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
167 					"%02d", pt, ptlim);
168 				continue;
169 			case 'c':
170 				{
171 				int warn2 = IN_SOME;
172 
173 				pt = _fmt(Locale->d_t_fmt, t, pt, ptlim, warnp);
174 				if (warn2 == IN_ALL)
175 					warn2 = IN_THIS;
176 				if (warn2 > *warnp)
177 					*warnp = warn2;
178 				}
179 				continue;
180 			case 'D':
181 				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
182 				continue;
183 			case 'd':
184 				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
185 				continue;
186 			case 'E':
187 			case 'O':
188 				/*
189 				** C99 locale modifiers.
190 				** The sequences
191 				**	%Ec %EC %Ex %EX %Ey %EY
192 				**	%Od %oe %OH %OI %Om %OM
193 				**	%OS %Ou %OU %OV %Ow %OW %Oy
194 				** are supposed to provide alternate
195 				** representations.
196 				*/
197 				goto label;
198 			case 'e':
199 				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
200 				continue;
201 			case 'F':
202 				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
203 				continue;
204 			case 'H':
205 				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
206 				continue;
207 			case 'I':
208 				pt = _conv((t->tm_hour % 12) ?
209 					(t->tm_hour % 12) : 12,
210 					"%02d", pt, ptlim);
211 				continue;
212 			case 'j':
213 				pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
214 				continue;
215 			case 'k':
216 				/*
217 				** This used to be...
218 				**	_conv(t->tm_hour % 12 ?
219 				**		t->tm_hour % 12 : 12, 2, ' ');
220 				** ...and has been changed to the below to
221 				** match SunOS 4.1.1 and Arnold Robbins'
222 				** strftime version 3.0.  That is, "%k" and
223 				** "%l" have been swapped.
224 				** (ado, 1993-05-24)
225 				*/
226 				pt = _conv(t->tm_hour, "%2d", pt, ptlim);
227 				continue;
228 #ifdef KITCHEN_SINK
229 			case 'K':
230 				/*
231 				** After all this time, still unclaimed!
232 				*/
233 				pt = _add("kitchen sink", pt, ptlim);
234 				continue;
235 #endif /* defined KITCHEN_SINK */
236 			case 'l':
237 				/*
238 				** This used to be...
239 				**	_conv(t->tm_hour, 2, ' ');
240 				** ...and has been changed to the below to
241 				** match SunOS 4.1.1 and Arnold Robbin's
242 				** strftime version 3.0.  That is, "%k" and
243 				** "%l" have been swapped.
244 				** (ado, 1993-05-24)
245 				*/
246 				pt = _conv((t->tm_hour % 12) ?
247 					(t->tm_hour % 12) : 12,
248 					"%2d", pt, ptlim);
249 				continue;
250 			case 'M':
251 				pt = _conv(t->tm_min, "%02d", pt, ptlim);
252 				continue;
253 			case 'm':
254 				pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
255 				continue;
256 			case 'n':
257 				pt = _add("\n", pt, ptlim);
258 				continue;
259 			case 'p':
260 				pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
261 					Locale->am_pm[1] :
262 					Locale->am_pm[0],
263 					pt, ptlim);
264 				continue;
265 			case 'R':
266 				pt = _fmt("%H:%M", t, pt, ptlim, warnp);
267 				continue;
268 			case 'r':
269 				pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
270 				continue;
271 			case 'S':
272 				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
273 				continue;
274 			case 's':
275 				{
276 					struct tm	tm;
277 					char		buf[INT_STRLEN_MAXIMUM(
278 								time_t) + 1];
279 					time_t		mkt;
280 
281 					tm = *t;
282 					mkt = mktime(&tm);
283 					/* CONSTCOND */
284 					if (TYPE_SIGNED(time_t))
285 						(void) sprintf(buf, "%ld",
286 							(long) mkt);
287 					else	(void) sprintf(buf, "%lu",
288 							(unsigned long) mkt);
289 					pt = _add(buf, pt, ptlim);
290 				}
291 				continue;
292 			case 'T':
293 				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
294 				continue;
295 			case 't':
296 				pt = _add("\t", pt, ptlim);
297 				continue;
298 			case 'U':
299 				pt = _conv((t->tm_yday + DAYSPERWEEK -
300 					t->tm_wday) / DAYSPERWEEK,
301 					"%02d", pt, ptlim);
302 				continue;
303 			case 'u':
304 				/*
305 				** From Arnold Robbins' strftime version 3.0:
306 				** "ISO 8601: Weekday as a decimal number
307 				** [1 (Monday) - 7]"
308 				** (ado, 1993-05-24)
309 				*/
310 				pt = _conv((t->tm_wday == 0) ?
311 					DAYSPERWEEK : t->tm_wday,
312 					"%d", pt, ptlim);
313 				continue;
314 			case 'V':	/* ISO 8601 week number */
315 			case 'G':	/* ISO 8601 year (four digits) */
316 			case 'g':	/* ISO 8601 year (two digits) */
317 /*
318 ** From Arnold Robbins' strftime version 3.0:  "the week number of the
319 ** year (the first Monday as the first day of week 1) as a decimal number
320 ** (01-53)."
321 ** (ado, 1993-05-24)
322 **
323 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
324 ** "Week 01 of a year is per definition the first week which has the
325 ** Thursday in this year, which is equivalent to the week which contains
326 ** the fourth day of January. In other words, the first week of a new year
327 ** is the week which has the majority of its days in the new year. Week 01
328 ** might also contain days from the previous year and the week before week
329 ** 01 of a year is the last week (52 or 53) of the previous year even if
330 ** it contains days from the new year. A week starts with Monday (day 1)
331 ** and ends with Sunday (day 7).  For example, the first week of the year
332 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
333 ** (ado, 1996-01-02)
334 */
335 				{
336 					int	year;
337 					int	yday;
338 					int	wday;
339 					int	w;
340 
341 					year = t->tm_year + TM_YEAR_BASE;
342 					yday = t->tm_yday;
343 					wday = t->tm_wday;
344 					for ( ; ; ) {
345 						int	len;
346 						int	bot;
347 						int	top;
348 
349 						len = isleap(year) ?
350 							DAYSPERLYEAR :
351 							DAYSPERNYEAR;
352 						/*
353 						** What yday (-3 ... 3) does
354 						** the ISO year begin on?
355 						*/
356 						bot = ((yday + 11 - wday) %
357 							DAYSPERWEEK) - 3;
358 						/*
359 						** What yday does the NEXT
360 						** ISO year begin on?
361 						*/
362 						top = bot -
363 							(len % DAYSPERWEEK);
364 						if (top < -3)
365 							top += DAYSPERWEEK;
366 						top += len;
367 						if (yday >= top) {
368 							++year;
369 							w = 1;
370 							break;
371 						}
372 						if (yday >= bot) {
373 							w = 1 + ((yday - bot) /
374 								DAYSPERWEEK);
375 							break;
376 						}
377 						--year;
378 						yday += isleap(year) ?
379 							DAYSPERLYEAR :
380 							DAYSPERNYEAR;
381 					}
382 #ifdef XPG4_1994_04_09
383 					if ((w == 52
384 					     && t->tm_mon == TM_JANUARY)
385 					    || (w == 1
386 						&& t->tm_mon == TM_DECEMBER))
387 						w = 53;
388 #endif /* defined XPG4_1994_04_09 */
389 					if (*format == 'V')
390 						pt = _conv(w, "%02d",
391 							pt, ptlim);
392 					else if (*format == 'g') {
393 						*warnp = IN_ALL;
394 						pt = _conv(year % 100, "%02d",
395 							pt, ptlim);
396 					} else	pt = _conv(year, "%04d",
397 							pt, ptlim);
398 				}
399 				continue;
400 			case 'v':
401 				/*
402 				** From Arnold Robbins' strftime version 3.0:
403 				** "date as dd-bbb-YYYY"
404 				** (ado, 1993-05-24)
405 				*/
406 				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
407 				continue;
408 			case 'W':
409 				pt = _conv((t->tm_yday + DAYSPERWEEK -
410 					(t->tm_wday ?
411 					(t->tm_wday - 1) :
412 					(DAYSPERWEEK - 1))) / DAYSPERWEEK,
413 					"%02d", pt, ptlim);
414 				continue;
415 			case 'w':
416 				pt = _conv(t->tm_wday, "%d", pt, ptlim);
417 				continue;
418 			case 'X':
419 				pt = _fmt(Locale->t_fmt, t, pt, ptlim, warnp);
420 				continue;
421 			case 'x':
422 				{
423 				int	warn2 = IN_SOME;
424 
425 				pt = _fmt(Locale->d_fmt, t, pt, ptlim, &warn2);
426 				if (warn2 == IN_ALL)
427 					warn2 = IN_THIS;
428 				if (warn2 > *warnp)
429 					*warnp = warn2;
430 				}
431 				continue;
432 			case 'y':
433 				*warnp = IN_ALL;
434 				pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
435 					"%02d", pt, ptlim);
436 				continue;
437 			case 'Y':
438 				pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
439 					pt, ptlim);
440 				continue;
441 			case 'Z':
442 #ifdef TM_ZONE
443 				if (t->TM_ZONE != NULL)
444 					pt = _add(t->TM_ZONE, pt, ptlim);
445 				else
446 #endif /* defined TM_ZONE */
447 				if (t->tm_isdst >= 0)
448 					pt = _add(tzname[t->tm_isdst != 0],
449 						pt, ptlim);
450 				/*
451 				** C99 says that %Z must be replaced by the
452 				** empty string if the time zone is not
453 				** determinable.
454 				*/
455 				continue;
456 			case 'z':
457 				{
458 				int		diff;
459 				char const *	sign;
460 
461 				if (t->tm_isdst < 0)
462 					continue;
463 #ifdef TM_GMTOFF
464 				diff = (int)t->TM_GMTOFF;
465 #else /* !defined TM_GMTOFF */
466 				/*
467 				** C99 says that the UTC offset must
468 				** be computed by looking only at
469 				** tm_isdst.  This requirement is
470 				** incorrect, since it means the code
471 				** must rely on magic (in this case
472 				** altzone and timezone), and the
473 				** magic might not have the correct
474 				** offset.  Doing things correctly is
475 				** tricky and requires disobeying C99;
476 				** see GNU C strftime for details.
477 				** For now, punt and conform to the
478 				** standard, even though it's incorrect.
479 				**
480 				** C99 says that %z must be replaced by the
481 				** empty string if the time zone is not
482 				** determinable, so output nothing if the
483 				** appropriate variables are not available.
484 				*/
485 				if (t->tm_isdst == 0)
486 #ifdef USG_COMPAT
487 					diff = -timezone;
488 #else /* !defined USG_COMPAT */
489 					continue;
490 #endif /* !defined USG_COMPAT */
491 				else
492 #ifdef ALTZONE
493 					diff = -altzone;
494 #else /* !defined ALTZONE */
495 					continue;
496 #endif /* !defined ALTZONE */
497 #endif /* !defined TM_GMTOFF */
498 				if (diff < 0) {
499 					sign = "-";
500 					diff = -diff;
501 				} else	sign = "+";
502 				pt = _add(sign, pt, ptlim);
503 				diff /= 60;
504 				pt = _conv((diff/60)*100 + diff%60,
505 					"%04d", pt, ptlim);
506 				}
507 				continue;
508 #if 0
509 			case '+':
510 				pt = _fmt(Locale->date_fmt, t, pt, ptlim,
511 					warnp);
512 				continue;
513 #endif
514 			case '%':
515 			/*
516 			** X311J/88-090 (4.12.3.5): if conversion char is
517 			** undefined, behavior is undefined.  Print out the
518 			** character itself as printf(3) also does.
519 			*/
520 			default:
521 				break;
522 			}
523 		}
524 		if (pt == ptlim)
525 			break;
526 		*pt++ = *format;
527 	}
528 	return pt;
529 }
530 
531 static char *
532 _conv(n, format, pt, ptlim)
533 const int		n;
534 const char * const	format;
535 char * const		pt;
536 const char * const	ptlim;
537 {
538 	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
539 
540 	(void) sprintf(buf, format, n);
541 	return _add(buf, pt, ptlim);
542 }
543 
544 static char *
545 _add(str, pt, ptlim)
546 const char *		str;
547 char *			pt;
548 const char * const	ptlim;
549 {
550 	while (pt < ptlim && (*pt = *str++) != '\0')
551 		++pt;
552 	return pt;
553 }
554