]>
Commit | Line | Data |
---|---|---|
5b2abdfb 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 | #ifdef LIBC_RCS | |
19 | static const char rcsid[] = | |
20 | "$FreeBSD: src/lib/libc/stdtime/strftime.c,v 1.25.2.3 2001/02/18 04:06:49 kris Exp $"; | |
21 | #endif | |
22 | ||
23 | #ifndef lint | |
24 | #ifndef NOID | |
25 | static const char elsieid[] = "@(#)strftime.c 7.38"; | |
26 | /* | |
27 | ** Based on the UCB version with the ID appearing below. | |
28 | ** This is ANSIish only when "multibyte character == plain character". | |
29 | */ | |
30 | #endif /* !defined NOID */ | |
31 | #endif /* !defined lint */ | |
32 | ||
33 | #include "private.h" | |
34 | ||
35 | #ifndef LIBC_SCCS | |
36 | #ifndef lint | |
37 | static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; | |
38 | #endif /* !defined lint */ | |
39 | #endif /* !defined LIBC_SCCS */ | |
40 | ||
41 | #include "tzfile.h" | |
42 | #include <fcntl.h> | |
43 | #include <sys/stat.h> | |
44 | #include "timelocal.h" | |
45 | ||
46 | static char * _add P((const char *, char *, const char *)); | |
47 | static char * _conv P((int, const char *, char *, const char *)); | |
48 | static char * _fmt P((const char *, const struct tm *, char *, const char *)); | |
49 | ||
50 | size_t strftime P((char *, size_t, const char *, const struct tm *)); | |
51 | ||
52 | extern char * tzname[]; | |
53 | ||
54 | size_t | |
55 | strftime(s, maxsize, format, t) | |
56 | char *const s; | |
57 | const size_t maxsize; | |
58 | const char *const format; | |
59 | const struct tm *const t; | |
60 | { | |
61 | char *p; | |
62 | ||
63 | tzset(); | |
64 | p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize); | |
65 | if (p == s + maxsize) | |
66 | return 0; | |
67 | *p = '\0'; | |
68 | return p - s; | |
69 | } | |
70 | ||
71 | static char * | |
72 | _fmt(format, t, pt, ptlim) | |
73 | const char *format; | |
74 | const struct tm *const t; | |
75 | char *pt; | |
76 | const char *const ptlim; | |
77 | { | |
78 | int Ealternative, Oalternative; | |
79 | ||
80 | for ( ; *format; ++format) { | |
81 | if (*format == '%') { | |
82 | Ealternative = 0; | |
83 | Oalternative = 0; | |
84 | label: | |
85 | switch (*++format) { | |
86 | case '\0': | |
87 | --format; | |
88 | break; | |
89 | case 'A': | |
90 | pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? | |
91 | "?" : Locale->weekday[t->tm_wday], | |
92 | pt, ptlim); | |
93 | continue; | |
94 | case 'a': | |
95 | pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? | |
96 | "?" : Locale->wday[t->tm_wday], | |
97 | pt, ptlim); | |
98 | continue; | |
99 | case 'B': | |
100 | pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? | |
101 | "?" : (Oalternative ? Locale->alt_month : | |
102 | Locale->month)[t->tm_mon], | |
103 | pt, ptlim); | |
104 | continue; | |
105 | case 'b': | |
106 | case 'h': | |
107 | pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? | |
108 | "?" : Locale->mon[t->tm_mon], | |
109 | pt, ptlim); | |
110 | continue; | |
111 | case 'C': | |
112 | /* | |
113 | ** %C used to do a... | |
114 | ** _fmt("%a %b %e %X %Y", t); | |
115 | ** ...whereas now POSIX 1003.2 calls for | |
116 | ** something completely different. | |
117 | ** (ado, 5/24/93) | |
118 | */ | |
119 | pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, | |
120 | "%02d", pt, ptlim); | |
121 | continue; | |
122 | case 'c': | |
123 | /* NOTE: c_fmt is intentionally ignored */ | |
124 | pt = _fmt("%a %Ef %T %Y", t, pt, ptlim); | |
125 | continue; | |
126 | case 'D': | |
127 | pt = _fmt("%m/%d/%y", t, pt, ptlim); | |
128 | continue; | |
129 | case 'd': | |
130 | pt = _conv(t->tm_mday, "%02d", pt, ptlim); | |
131 | continue; | |
132 | case 'E': | |
133 | if (Ealternative || Oalternative) | |
134 | break; | |
135 | Ealternative++; | |
136 | goto label; | |
137 | case 'O': | |
138 | /* | |
139 | ** POSIX locale extensions, a la | |
140 | ** Arnold Robbins' strftime version 3.0. | |
141 | ** The sequences | |
142 | ** %Ec %EC %Ex %EX %Ey %EY | |
143 | ** %Od %oe %OH %OI %Om %OM | |
144 | ** %OS %Ou %OU %OV %Ow %OW %Oy | |
145 | ** are supposed to provide alternate | |
146 | ** representations. | |
147 | ** (ado, 5/24/93) | |
148 | ** | |
149 | ** FreeBSD extensions | |
150 | ** %OB %Ef %EF | |
151 | */ | |
152 | if (Ealternative || Oalternative) | |
153 | break; | |
154 | Oalternative++; | |
155 | goto label; | |
156 | case 'e': | |
157 | pt = _conv(t->tm_mday, "%2d", pt, ptlim); | |
158 | continue; | |
159 | case 'f': | |
160 | if (!Ealternative) | |
161 | break; | |
162 | pt = _fmt(Locale->Ef_fmt, t, pt, ptlim); | |
163 | continue; | |
164 | case 'F': | |
165 | if (!Ealternative) | |
166 | break; | |
167 | pt = _fmt(Locale->EF_fmt, t, pt, ptlim); | |
168 | continue; | |
169 | case 'H': | |
170 | pt = _conv(t->tm_hour, "%02d", pt, ptlim); | |
171 | continue; | |
172 | case 'I': | |
173 | pt = _conv((t->tm_hour % 12) ? | |
174 | (t->tm_hour % 12) : 12, | |
175 | "%02d", pt, ptlim); | |
176 | continue; | |
177 | case 'j': | |
178 | pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); | |
179 | continue; | |
180 | case 'k': | |
181 | /* | |
182 | ** This used to be... | |
183 | ** _conv(t->tm_hour % 12 ? | |
184 | ** t->tm_hour % 12 : 12, 2, ' '); | |
185 | ** ...and has been changed to the below to | |
186 | ** match SunOS 4.1.1 and Arnold Robbins' | |
187 | ** strftime version 3.0. That is, "%k" and | |
188 | ** "%l" have been swapped. | |
189 | ** (ado, 5/24/93) | |
190 | */ | |
191 | pt = _conv(t->tm_hour, "%2d", pt, ptlim); | |
192 | continue; | |
193 | #ifdef KITCHEN_SINK | |
194 | case 'K': | |
195 | /* | |
196 | ** After all this time, still unclaimed! | |
197 | */ | |
198 | pt = _add("kitchen sink", pt, ptlim); | |
199 | continue; | |
200 | #endif /* defined KITCHEN_SINK */ | |
201 | case 'l': | |
202 | /* | |
203 | ** This used to be... | |
204 | ** _conv(t->tm_hour, 2, ' '); | |
205 | ** ...and has been changed to the below to | |
206 | ** match SunOS 4.1.1 and Arnold Robbin's | |
207 | ** strftime version 3.0. That is, "%k" and | |
208 | ** "%l" have been swapped. | |
209 | ** (ado, 5/24/93) | |
210 | */ | |
211 | pt = _conv((t->tm_hour % 12) ? | |
212 | (t->tm_hour % 12) : 12, | |
213 | "%2d", pt, ptlim); | |
214 | continue; | |
215 | case 'M': | |
216 | pt = _conv(t->tm_min, "%02d", pt, ptlim); | |
217 | continue; | |
218 | case 'm': | |
219 | pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); | |
220 | continue; | |
221 | case 'n': | |
222 | pt = _add("\n", pt, ptlim); | |
223 | continue; | |
224 | case 'p': | |
225 | pt = _add((t->tm_hour >= 12) ? | |
226 | Locale->pm : | |
227 | Locale->am, | |
228 | pt, ptlim); | |
229 | continue; | |
230 | case 'R': | |
231 | pt = _fmt("%H:%M", t, pt, ptlim); | |
232 | continue; | |
233 | case 'r': | |
234 | pt = _fmt("%I:%M:%S %p", t, pt, ptlim); | |
235 | continue; | |
236 | case 'S': | |
237 | pt = _conv(t->tm_sec, "%02d", pt, ptlim); | |
238 | continue; | |
239 | case 's': | |
240 | { | |
241 | struct tm tm; | |
242 | char buf[INT_STRLEN_MAXIMUM( | |
243 | time_t) + 1]; | |
244 | time_t mkt; | |
245 | ||
246 | tm = *t; | |
247 | mkt = mktime(&tm); | |
248 | if (TYPE_SIGNED(time_t)) | |
249 | (void) sprintf(buf, "%ld", | |
250 | (long) mkt); | |
251 | else (void) sprintf(buf, "%lu", | |
252 | (unsigned long) mkt); | |
253 | pt = _add(buf, pt, ptlim); | |
254 | } | |
255 | continue; | |
256 | case 'T': | |
257 | pt = _fmt("%H:%M:%S", t, pt, ptlim); | |
258 | continue; | |
259 | case 't': | |
260 | pt = _add("\t", pt, ptlim); | |
261 | continue; | |
262 | case 'U': | |
263 | pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7, | |
264 | "%02d", pt, ptlim); | |
265 | continue; | |
266 | case 'u': | |
267 | /* | |
268 | ** From Arnold Robbins' strftime version 3.0: | |
269 | ** "ISO 8601: Weekday as a decimal number | |
270 | ** [1 (Monday) - 7]" | |
271 | ** (ado, 5/24/93) | |
272 | */ | |
273 | pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday, | |
274 | "%d", pt, ptlim); | |
275 | continue; | |
276 | case 'V': /* ISO 8601 week number */ | |
277 | case 'G': /* ISO 8601 year (four digits) */ | |
278 | case 'g': /* ISO 8601 year (two digits) */ | |
279 | /* | |
280 | ** From Arnold Robbins' strftime version 3.0: "the week number of the | |
281 | ** year (the first Monday as the first day of week 1) as a decimal number | |
282 | ** (01-53)." | |
283 | ** (ado, 1993-05-24) | |
284 | ** | |
285 | ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: | |
286 | ** "Week 01 of a year is per definition the first week which has the | |
287 | ** Thursday in this year, which is equivalent to the week which contains | |
288 | ** the fourth day of January. In other words, the first week of a new year | |
289 | ** is the week which has the majority of its days in the new year. Week 01 | |
290 | ** might also contain days from the previous year and the week before week | |
291 | ** 01 of a year is the last week (52 or 53) of the previous year even if | |
292 | ** it contains days from the new year. A week starts with Monday (day 1) | |
293 | ** and ends with Sunday (day 7). For example, the first week of the year | |
294 | ** 1997 lasts from 1996-12-30 to 1997-01-05..." | |
295 | ** (ado, 1996-01-02) | |
296 | */ | |
297 | { | |
298 | int year; | |
299 | int yday; | |
300 | int wday; | |
301 | int w; | |
302 | ||
303 | year = t->tm_year + TM_YEAR_BASE; | |
304 | yday = t->tm_yday; | |
305 | wday = t->tm_wday; | |
306 | for ( ; ; ) { | |
307 | int len; | |
308 | int bot; | |
309 | int top; | |
310 | ||
311 | len = isleap(year) ? | |
312 | DAYSPERLYEAR : | |
313 | DAYSPERNYEAR; | |
314 | /* | |
315 | ** What yday (-3 ... 3) does | |
316 | ** the ISO year begin on? | |
317 | */ | |
318 | bot = ((yday + 11 - wday) % | |
319 | DAYSPERWEEK) - 3; | |
320 | /* | |
321 | ** What yday does the NEXT | |
322 | ** ISO year begin on? | |
323 | */ | |
324 | top = bot - | |
325 | (len % DAYSPERWEEK); | |
326 | if (top < -3) | |
327 | top += DAYSPERWEEK; | |
328 | top += len; | |
329 | if (yday >= top) { | |
330 | ++year; | |
331 | w = 1; | |
332 | break; | |
333 | } | |
334 | if (yday >= bot) { | |
335 | w = 1 + ((yday - bot) / | |
336 | DAYSPERWEEK); | |
337 | break; | |
338 | } | |
339 | --year; | |
340 | yday += isleap(year) ? | |
341 | DAYSPERLYEAR : | |
342 | DAYSPERNYEAR; | |
343 | } | |
344 | #ifdef XPG4_1994_04_09 | |
345 | if ((w == 52 | |
346 | && t->tm_mon == TM_JANUARY) | |
347 | || (w == 1 | |
348 | && t->tm_mon == TM_DECEMBER)) | |
349 | w = 53; | |
350 | #endif /* defined XPG4_1994_04_09 */ | |
351 | if (*format == 'V') | |
352 | pt = _conv(w, "%02d", | |
353 | pt, ptlim); | |
354 | else if (*format == 'g') { | |
355 | pt = _conv(year % 100, "%02d", | |
356 | pt, ptlim); | |
357 | } else pt = _conv(year, "%04d", | |
358 | pt, ptlim); | |
359 | } | |
360 | continue; | |
361 | case 'v': | |
362 | /* | |
363 | ** From Arnold Robbins' strftime version 3.0: | |
364 | ** "date as dd-bbb-YYYY" | |
365 | ** (ado, 5/24/93) | |
366 | */ | |
367 | pt = _fmt("%e-%b-%Y", t, pt, ptlim); | |
368 | continue; | |
369 | case 'W': | |
370 | pt = _conv((t->tm_yday + 7 - | |
371 | (t->tm_wday ? | |
372 | (t->tm_wday - 1) : 6)) / 7, | |
373 | "%02d", pt, ptlim); | |
374 | continue; | |
375 | case 'w': | |
376 | pt = _conv(t->tm_wday, "%d", pt, ptlim); | |
377 | continue; | |
378 | case 'X': | |
379 | pt = _fmt(Locale->X_fmt, t, pt, ptlim); | |
380 | continue; | |
381 | case 'x': | |
382 | pt = _fmt(Locale->x_fmt, t, pt, ptlim); | |
383 | continue; | |
384 | case 'y': | |
385 | pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, | |
386 | "%02d", pt, ptlim); | |
387 | continue; | |
388 | case 'Y': | |
389 | pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", | |
390 | pt, ptlim); | |
391 | continue; | |
392 | case 'Z': | |
393 | if (t->tm_zone != NULL) | |
394 | pt = _add(t->tm_zone, pt, ptlim); | |
395 | else | |
396 | if (t->tm_isdst == 0 || t->tm_isdst == 1) { | |
397 | pt = _add(tzname[t->tm_isdst], | |
398 | pt, ptlim); | |
399 | } else pt = _add("?", pt, ptlim); | |
400 | continue; | |
401 | case 'z': | |
402 | { | |
403 | long absoff; | |
404 | if (t->tm_gmtoff >= 0) { | |
405 | absoff = t->tm_gmtoff; | |
406 | pt = _add("+", pt, ptlim); | |
407 | } else { | |
408 | absoff = -t->tm_gmtoff; | |
409 | pt = _add("-", pt, ptlim); | |
410 | } | |
411 | pt = _conv(absoff / 3600, "%02d", | |
412 | pt, ptlim); | |
413 | pt = _conv((absoff % 3600) / 60, "%02d", | |
414 | pt, ptlim); | |
415 | }; | |
416 | continue; | |
417 | case '+': | |
418 | pt = _fmt(Locale->date_fmt, t, pt, ptlim); | |
419 | continue; | |
420 | case '%': | |
421 | /* | |
422 | * X311J/88-090 (4.12.3.5): if conversion char is | |
423 | * undefined, behavior is undefined. Print out the | |
424 | * character itself as printf(3) also does. | |
425 | */ | |
426 | default: | |
427 | break; | |
428 | } | |
429 | } | |
430 | if (pt == ptlim) | |
431 | break; | |
432 | *pt++ = *format; | |
433 | } | |
434 | return pt; | |
435 | } | |
436 | ||
437 | static char * | |
438 | _conv(n, format, pt, ptlim) | |
439 | const int n; | |
440 | const char *const format; | |
441 | char *const pt; | |
442 | const char *const ptlim; | |
443 | { | |
444 | char buf[INT_STRLEN_MAXIMUM(int) + 1]; | |
445 | ||
446 | (void) sprintf(buf, format, n); | |
447 | return _add(buf, pt, ptlim); | |
448 | } | |
449 | ||
450 | static char * | |
451 | _add(str, pt, ptlim) | |
452 | const char *str; | |
453 | char *pt; | |
454 | const char *const ptlim; | |
455 | { | |
456 | while (pt < ptlim && (*pt = *str++) != '\0') | |
457 | ++pt; | |
458 | return pt; | |
459 | } |