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