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