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