]>
Commit | Line | Data |
---|---|---|
2e4498b3 | 1 | /* strftime - formatted time and date to a string */ |
7117c2d2 JA |
2 | /* |
3 | * Modified slightly by Chet Ramey for inclusion in Bash | |
4 | */ | |
7117c2d2 JA |
5 | /* |
6 | * strftime.c | |
7 | * | |
8 | * Public-domain implementation of ISO C library routine. | |
9 | * | |
10 | * If you can't do prototypes, get GCC. | |
11 | * | |
12 | * The C99 standard now specifies just about all of the formats | |
13 | * that were additional in the earlier versions of this file. | |
14 | * | |
15 | * For extensions from SunOS, add SUNOS_EXT. | |
16 | * For extensions from HP/UX, add HPUX_EXT. | |
17 | * For VMS dates, add VMS_EXT. | |
18 | * For complete POSIX semantics, add POSIX_SEMANTICS. | |
19 | * | |
20 | * The code for %c, %x, and %X follows the C99 specification for | |
21 | * the "C" locale. | |
22 | * | |
23 | * This version ignores LOCALE information. | |
24 | * It also doesn't worry about multi-byte characters. | |
25 | * So there. | |
26 | * | |
27 | * This file is also shipped with GAWK (GNU Awk), gawk specific bits of | |
28 | * code are included if GAWK is defined. | |
29 | * | |
30 | * Arnold Robbins | |
31 | * January, February, March, 1991 | |
32 | * Updated March, April 1992 | |
33 | * Updated April, 1993 | |
34 | * Updated February, 1994 | |
35 | * Updated May, 1994 | |
36 | * Updated January, 1995 | |
37 | * Updated September, 1995 | |
38 | * Updated January, 1996 | |
39 | * Updated July, 1997 | |
40 | * Updated October, 1999 | |
41 | * Updated September, 2000 | |
42 | * | |
43 | * Fixes from ado@elsie.nci.nih.gov, | |
44 | * February 1991, May 1992 | |
45 | * Fixes from Tor Lillqvist tml@tik.vtt.fi, | |
46 | * May 1993 | |
47 | * Further fixes from ado@elsie.nci.nih.gov, | |
48 | * February 1994 | |
49 | * %z code from chip@chinacat.unicom.com, | |
50 | * Applied September 1995 | |
51 | * %V code fixed (again) and %G, %g added, | |
52 | * January 1996 | |
53 | * %v code fixed, better configuration, | |
54 | * July 1997 | |
55 | * Moved to C99 specification. | |
56 | * September 2000 | |
57 | */ | |
58 | #include <config.h> | |
59 | ||
60 | #ifndef GAWK | |
61 | #include <stdio.h> | |
62 | #include <ctype.h> | |
63 | #include <time.h> | |
64 | #endif | |
65 | #if defined(TM_IN_SYS_TIME) | |
66 | #include <sys/types.h> | |
67 | #include <sys/time.h> | |
68 | #endif | |
69 | ||
70 | #include <stdlib.h> | |
71 | #include <string.h> | |
72 | ||
73 | /* defaults: season to taste */ | |
74 | #define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ | |
75 | #define VMS_EXT 1 /* include %v for VMS date format */ | |
76 | #define HPUX_EXT 1 /* non-conflicting stuff in HP-UX date */ | |
77 | #ifndef GAWK | |
78 | #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ | |
79 | #endif | |
80 | ||
81 | #undef strchr /* avoid AIX weirdness */ | |
82 | ||
de3341d1 CR |
83 | #if defined (SHELL) |
84 | extern char *get_string_value (const char *); | |
85 | #endif | |
86 | ||
7117c2d2 JA |
87 | extern void tzset(void); |
88 | static int weeknumber(const struct tm *timeptr, int firstweekday); | |
89 | static int iso8601wknum(const struct tm *timeptr); | |
90 | ||
54cdd75a | 91 | #ifndef inline |
7117c2d2 JA |
92 | #ifdef __GNUC__ |
93 | #define inline __inline__ | |
94 | #else | |
95 | #define inline /**/ | |
96 | #endif | |
54cdd75a | 97 | #endif |
7117c2d2 JA |
98 | |
99 | #define range(low, item, hi) max(low, min(item, hi)) | |
100 | ||
101 | #if !defined(OS2) && !defined(MSDOS) && defined(HAVE_TZNAME) | |
102 | extern char *tzname[2]; | |
103 | extern int daylight; | |
5565fb1a | 104 | #if defined(SOLARIS) || defined(mips) || defined (M_UNIX) |
7117c2d2 JA |
105 | extern long int timezone, altzone; |
106 | #else | |
de3341d1 CR |
107 | # if defined (HPUX) |
108 | extern long int timezone; | |
109 | # else | |
7117c2d2 | 110 | extern int timezone, altzone; |
de3341d1 CR |
111 | # endif /* !HPUX */ |
112 | #endif /* !SOLARIS && !mips && !M_UNIX */ | |
7117c2d2 JA |
113 | #endif |
114 | ||
115 | #undef min /* just in case */ | |
116 | ||
117 | /* min --- return minimum of two numbers */ | |
118 | ||
119 | static inline int | |
120 | min(int a, int b) | |
121 | { | |
122 | return (a < b ? a : b); | |
123 | } | |
124 | ||
125 | #undef max /* also, just in case */ | |
126 | ||
127 | /* max --- return maximum of two numbers */ | |
128 | ||
129 | static inline int | |
130 | max(int a, int b) | |
131 | { | |
132 | return (a > b ? a : b); | |
133 | } | |
134 | ||
135 | /* strftime --- produce formatted time */ | |
136 | ||
137 | size_t | |
138 | strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr) | |
139 | { | |
140 | char *endp = s + maxsize; | |
141 | char *start = s; | |
142 | auto char tbuf[100]; | |
143 | long off; | |
144 | int i, w, y; | |
145 | static short first = 1; | |
146 | #ifdef POSIX_SEMANTICS | |
147 | static char *savetz = NULL; | |
148 | static int savetzlen = 0; | |
149 | char *tz; | |
150 | #endif /* POSIX_SEMANTICS */ | |
151 | #ifndef HAVE_TM_ZONE | |
152 | #ifndef HAVE_TM_NAME | |
153 | #ifndef HAVE_TZNAME | |
154 | extern char *timezone(); | |
155 | struct timeval tv; | |
156 | struct timezone zone; | |
157 | #endif /* HAVE_TZNAME */ | |
158 | #endif /* HAVE_TM_NAME */ | |
159 | #endif /* HAVE_TM_ZONE */ | |
160 | ||
161 | /* various tables, useful in North America */ | |
162 | static const char *days_a[] = { | |
163 | "Sun", "Mon", "Tue", "Wed", | |
164 | "Thu", "Fri", "Sat", | |
165 | }; | |
166 | static const char *days_l[] = { | |
167 | "Sunday", "Monday", "Tuesday", "Wednesday", | |
168 | "Thursday", "Friday", "Saturday", | |
169 | }; | |
170 | static const char *months_a[] = { | |
171 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", | |
172 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", | |
173 | }; | |
174 | static const char *months_l[] = { | |
175 | "January", "February", "March", "April", | |
176 | "May", "June", "July", "August", "September", | |
177 | "October", "November", "December", | |
178 | }; | |
179 | static const char *ampm[] = { "AM", "PM", }; | |
180 | ||
181 | if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0) | |
182 | return 0; | |
183 | ||
184 | /* quick check if we even need to bother */ | |
185 | if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) | |
186 | return 0; | |
187 | ||
188 | #ifndef POSIX_SEMANTICS | |
189 | if (first) { | |
190 | tzset(); | |
191 | first = 0; | |
192 | } | |
193 | #else /* POSIX_SEMANTICS */ | |
194 | #if defined (SHELL) | |
195 | tz = get_string_value ("TZ"); | |
196 | #else | |
197 | tz = getenv("TZ"); | |
198 | #endif | |
199 | if (first) { | |
200 | if (tz != NULL) { | |
201 | int tzlen = strlen(tz); | |
202 | ||
203 | savetz = (char *) malloc(tzlen + 1); | |
204 | if (savetz != NULL) { | |
205 | savetzlen = tzlen + 1; | |
206 | strcpy(savetz, tz); | |
207 | } | |
208 | } | |
209 | tzset(); | |
210 | first = 0; | |
211 | } | |
212 | /* if we have a saved TZ, and it is different, recapture and reset */ | |
213 | if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { | |
214 | i = strlen(tz) + 1; | |
215 | if (i > savetzlen) { | |
216 | savetz = (char *) realloc(savetz, i); | |
217 | if (savetz) { | |
218 | savetzlen = i; | |
219 | strcpy(savetz, tz); | |
220 | } | |
221 | } else | |
222 | strcpy(savetz, tz); | |
223 | tzset(); | |
224 | } | |
225 | #endif /* POSIX_SEMANTICS */ | |
226 | ||
227 | for (; *format && s < endp - 1; format++) { | |
228 | tbuf[0] = '\0'; | |
229 | if (*format != '%') { | |
230 | *s++ = *format; | |
231 | continue; | |
232 | } | |
233 | again: | |
234 | switch (*++format) { | |
235 | case '\0': | |
236 | *s++ = '%'; | |
237 | goto out; | |
238 | ||
239 | case '%': | |
240 | *s++ = '%'; | |
241 | continue; | |
242 | ||
243 | case 'a': /* abbreviated weekday name */ | |
244 | if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) | |
245 | strcpy(tbuf, "?"); | |
246 | else | |
247 | strcpy(tbuf, days_a[timeptr->tm_wday]); | |
248 | break; | |
249 | ||
250 | case 'A': /* full weekday name */ | |
251 | if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) | |
252 | strcpy(tbuf, "?"); | |
253 | else | |
254 | strcpy(tbuf, days_l[timeptr->tm_wday]); | |
255 | break; | |
256 | ||
257 | case 'b': /* abbreviated month name */ | |
258 | short_month: | |
259 | if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) | |
260 | strcpy(tbuf, "?"); | |
261 | else | |
262 | strcpy(tbuf, months_a[timeptr->tm_mon]); | |
263 | break; | |
264 | ||
265 | case 'B': /* full month name */ | |
266 | if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) | |
267 | strcpy(tbuf, "?"); | |
268 | else | |
269 | strcpy(tbuf, months_l[timeptr->tm_mon]); | |
270 | break; | |
271 | ||
272 | case 'c': /* appropriate date and time representation */ | |
273 | /* | |
274 | * This used to be: | |
275 | * | |
276 | * strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S %Y", timeptr); | |
277 | * | |
278 | * Now, per the ISO 1999 C standard, it this: | |
279 | */ | |
280 | strftime(tbuf, sizeof tbuf, "%A %B %d %T %Y", timeptr); | |
281 | break; | |
282 | ||
283 | case 'C': | |
284 | century: | |
285 | sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100); | |
286 | break; | |
287 | ||
288 | case 'd': /* day of the month, 01 - 31 */ | |
289 | i = range(1, timeptr->tm_mday, 31); | |
290 | sprintf(tbuf, "%02d", i); | |
291 | break; | |
292 | ||
293 | case 'D': /* date as %m/%d/%y */ | |
294 | strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); | |
295 | break; | |
296 | ||
297 | case 'e': /* day of month, blank padded */ | |
298 | sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31)); | |
299 | break; | |
300 | ||
301 | case 'E': | |
302 | /* POSIX (now C99) locale extensions, ignored for now */ | |
303 | goto again; | |
304 | ||
305 | case 'F': /* ISO 8601 date representation */ | |
306 | strftime(tbuf, sizeof tbuf, "%Y-%m-%d", timeptr); | |
307 | break; | |
308 | ||
309 | case 'g': | |
310 | case 'G': | |
311 | /* | |
312 | * Year of ISO week. | |
313 | * | |
314 | * If it's December but the ISO week number is one, | |
315 | * that week is in next year. | |
316 | * If it's January but the ISO week number is 52 or | |
317 | * 53, that week is in last year. | |
318 | * Otherwise, it's this year. | |
319 | */ | |
320 | w = iso8601wknum(timeptr); | |
321 | if (timeptr->tm_mon == 11 && w == 1) | |
322 | y = 1900 + timeptr->tm_year + 1; | |
323 | else if (timeptr->tm_mon == 0 && w >= 52) | |
324 | y = 1900 + timeptr->tm_year - 1; | |
325 | else | |
326 | y = 1900 + timeptr->tm_year; | |
327 | ||
328 | if (*format == 'G') | |
329 | sprintf(tbuf, "%d", y); | |
330 | else | |
331 | sprintf(tbuf, "%02d", y % 100); | |
332 | break; | |
333 | ||
334 | case 'h': /* abbreviated month name */ | |
335 | goto short_month; | |
336 | ||
337 | case 'H': /* hour, 24-hour clock, 00 - 23 */ | |
338 | i = range(0, timeptr->tm_hour, 23); | |
339 | sprintf(tbuf, "%02d", i); | |
340 | break; | |
341 | ||
342 | case 'I': /* hour, 12-hour clock, 01 - 12 */ | |
343 | i = range(0, timeptr->tm_hour, 23); | |
344 | if (i == 0) | |
345 | i = 12; | |
346 | else if (i > 12) | |
347 | i -= 12; | |
348 | sprintf(tbuf, "%02d", i); | |
349 | break; | |
350 | ||
351 | case 'j': /* day of the year, 001 - 366 */ | |
352 | sprintf(tbuf, "%03d", timeptr->tm_yday + 1); | |
353 | break; | |
354 | ||
355 | case 'm': /* month, 01 - 12 */ | |
356 | i = range(0, timeptr->tm_mon, 11); | |
357 | sprintf(tbuf, "%02d", i + 1); | |
358 | break; | |
359 | ||
360 | case 'M': /* minute, 00 - 59 */ | |
361 | i = range(0, timeptr->tm_min, 59); | |
362 | sprintf(tbuf, "%02d", i); | |
363 | break; | |
364 | ||
365 | case 'n': /* same as \n */ | |
366 | tbuf[0] = '\n'; | |
367 | tbuf[1] = '\0'; | |
368 | break; | |
369 | ||
370 | case 'O': | |
371 | /* POSIX (now C99) locale extensions, ignored for now */ | |
372 | goto again; | |
373 | ||
374 | case 'p': /* am or pm based on 12-hour clock */ | |
375 | i = range(0, timeptr->tm_hour, 23); | |
376 | if (i < 12) | |
377 | strcpy(tbuf, ampm[0]); | |
378 | else | |
379 | strcpy(tbuf, ampm[1]); | |
380 | break; | |
381 | ||
382 | case 'r': /* time as %I:%M:%S %p */ | |
383 | strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr); | |
384 | break; | |
385 | ||
386 | case 'R': /* time as %H:%M */ | |
387 | strftime(tbuf, sizeof tbuf, "%H:%M", timeptr); | |
388 | break; | |
389 | ||
390 | #if defined(HAVE_MKTIME) || defined(GAWK) | |
391 | case 's': /* time as seconds since the Epoch */ | |
392 | { | |
393 | struct tm non_const_timeptr; | |
394 | ||
395 | non_const_timeptr = *timeptr; | |
396 | sprintf(tbuf, "%ld", mktime(& non_const_timeptr)); | |
397 | break; | |
398 | } | |
399 | #endif /* defined(HAVE_MKTIME) || defined(GAWK) */ | |
400 | ||
401 | case 'S': /* second, 00 - 60 */ | |
402 | i = range(0, timeptr->tm_sec, 60); | |
403 | sprintf(tbuf, "%02d", i); | |
404 | break; | |
405 | ||
406 | case 't': /* same as \t */ | |
407 | tbuf[0] = '\t'; | |
408 | tbuf[1] = '\0'; | |
409 | break; | |
410 | ||
411 | case 'T': /* time as %H:%M:%S */ | |
412 | the_time: | |
413 | strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr); | |
414 | break; | |
415 | ||
416 | case 'u': | |
417 | /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ | |
418 | sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 : | |
419 | timeptr->tm_wday); | |
420 | break; | |
421 | ||
422 | case 'U': /* week of year, Sunday is first day of week */ | |
423 | sprintf(tbuf, "%02d", weeknumber(timeptr, 0)); | |
424 | break; | |
425 | ||
426 | case 'V': /* week of year according ISO 8601 */ | |
427 | sprintf(tbuf, "%02d", iso8601wknum(timeptr)); | |
428 | break; | |
429 | ||
430 | case 'w': /* weekday, Sunday == 0, 0 - 6 */ | |
431 | i = range(0, timeptr->tm_wday, 6); | |
432 | sprintf(tbuf, "%d", i); | |
433 | break; | |
434 | ||
435 | case 'W': /* week of year, Monday is first day of week */ | |
436 | sprintf(tbuf, "%02d", weeknumber(timeptr, 1)); | |
437 | break; | |
438 | ||
439 | case 'x': /* appropriate date representation */ | |
440 | strftime(tbuf, sizeof tbuf, "%A %B %d %Y", timeptr); | |
441 | break; | |
442 | ||
443 | case 'X': /* appropriate time representation */ | |
444 | goto the_time; | |
445 | break; | |
446 | ||
447 | case 'y': /* year without a century, 00 - 99 */ | |
448 | year: | |
449 | i = timeptr->tm_year % 100; | |
450 | sprintf(tbuf, "%02d", i); | |
451 | break; | |
452 | ||
453 | case 'Y': /* year with century */ | |
454 | fullyear: | |
455 | sprintf(tbuf, "%d", 1900 + timeptr->tm_year); | |
456 | break; | |
457 | ||
458 | /* | |
459 | * From: Chip Rosenthal <chip@chinacat.unicom.com> | |
460 | * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST) | |
461 | * | |
462 | * Warning: the %z [code] is implemented by inspecting the | |
463 | * timezone name conditional compile settings, and | |
464 | * inferring a method to get timezone offsets. I've tried | |
465 | * this code on a couple of machines, but I don't doubt | |
466 | * there is some system out there that won't like it. | |
467 | * Maybe the easiest thing to do would be to bracket this | |
468 | * with an #ifdef that can turn it off. The %z feature | |
469 | * would be an admittedly obscure one that most folks can | |
470 | * live without, but it would be a great help to those of | |
471 | * us that muck around with various message processors. | |
472 | */ | |
473 | case 'z': /* time zone offset east of GMT e.g. -0600 */ | |
c184f645 CR |
474 | if (timeptr->tm_isdst < 0) |
475 | break; | |
7117c2d2 JA |
476 | #ifdef HAVE_TM_NAME |
477 | /* | |
478 | * Systems with tm_name probably have tm_tzadj as | |
479 | * secs west of GMT. Convert to mins east of GMT. | |
480 | */ | |
481 | off = -timeptr->tm_tzadj / 60; | |
482 | #else /* !HAVE_TM_NAME */ | |
483 | #ifdef HAVE_TM_ZONE | |
484 | /* | |
485 | * Systems with tm_zone probably have tm_gmtoff as | |
486 | * secs east of GMT. Convert to mins east of GMT. | |
487 | */ | |
488 | off = timeptr->tm_gmtoff / 60; | |
489 | #else /* !HAVE_TM_ZONE */ | |
490 | #if HAVE_TZNAME | |
491 | /* | |
492 | * Systems with tzname[] probably have timezone as | |
493 | * secs west of GMT. Convert to mins east of GMT. | |
494 | */ | |
de3341d1 CR |
495 | # ifdef HPUX |
496 | off = -timezone / 60; | |
497 | # else | |
c184f645 | 498 | off = -(daylight ? altzone : timezone) / 60; |
de3341d1 | 499 | # endif /* !HPUX */ |
7117c2d2 | 500 | #else /* !HAVE_TZNAME */ |
de3341d1 | 501 | gettimeofday(& tv, & zone); |
7117c2d2 JA |
502 | off = -zone.tz_minuteswest; |
503 | #endif /* !HAVE_TZNAME */ | |
504 | #endif /* !HAVE_TM_ZONE */ | |
505 | #endif /* !HAVE_TM_NAME */ | |
506 | if (off < 0) { | |
507 | tbuf[0] = '-'; | |
508 | off = -off; | |
509 | } else { | |
510 | tbuf[0] = '+'; | |
511 | } | |
512 | sprintf(tbuf+1, "%02d%02d", off/60, off%60); | |
513 | break; | |
514 | ||
515 | case 'Z': /* time zone name or abbrevation */ | |
516 | #ifdef HAVE_TZNAME | |
517 | i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */ | |
518 | strcpy(tbuf, tzname[i]); | |
519 | #else | |
520 | #ifdef HAVE_TM_ZONE | |
521 | strcpy(tbuf, timeptr->tm_zone); | |
522 | #else | |
523 | #ifdef HAVE_TM_NAME | |
524 | strcpy(tbuf, timeptr->tm_name); | |
525 | #else | |
526 | gettimeofday(& tv, & zone); | |
527 | strcpy(tbuf, timezone(zone.tz_minuteswest, | |
528 | timeptr->tm_isdst > 0)); | |
529 | #endif /* HAVE_TM_NAME */ | |
530 | #endif /* HAVE_TM_ZONE */ | |
531 | #endif /* HAVE_TZNAME */ | |
532 | break; | |
533 | ||
534 | #ifdef SUNOS_EXT | |
535 | case 'k': /* hour, 24-hour clock, blank pad */ | |
536 | sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23)); | |
537 | break; | |
538 | ||
539 | case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ | |
540 | i = range(0, timeptr->tm_hour, 23); | |
541 | if (i == 0) | |
542 | i = 12; | |
543 | else if (i > 12) | |
544 | i -= 12; | |
545 | sprintf(tbuf, "%2d", i); | |
546 | break; | |
547 | #endif | |
548 | ||
549 | #ifdef HPUX_EXT | |
550 | case 'N': /* Emperor/Era name */ | |
551 | /* this is essentially the same as the century */ | |
552 | goto century; /* %C */ | |
553 | ||
554 | case 'o': /* Emperor/Era year */ | |
555 | goto year; /* %y */ | |
556 | #endif /* HPUX_EXT */ | |
557 | ||
558 | ||
559 | #ifdef VMS_EXT | |
560 | case 'v': /* date as dd-bbb-YYYY */ | |
561 | sprintf(tbuf, "%2d-%3.3s-%4d", | |
562 | range(1, timeptr->tm_mday, 31), | |
563 | months_a[range(0, timeptr->tm_mon, 11)], | |
564 | timeptr->tm_year + 1900); | |
565 | for (i = 3; i < 6; i++) | |
566 | if (islower(tbuf[i])) | |
567 | tbuf[i] = toupper(tbuf[i]); | |
568 | break; | |
569 | #endif | |
570 | ||
571 | default: | |
572 | tbuf[0] = '%'; | |
573 | tbuf[1] = *format; | |
574 | tbuf[2] = '\0'; | |
575 | break; | |
576 | } | |
577 | i = strlen(tbuf); | |
578 | if (i) { | |
579 | if (s + i < endp - 1) { | |
580 | strcpy(s, tbuf); | |
581 | s += i; | |
582 | } else | |
583 | return 0; | |
584 | } | |
585 | } | |
586 | out: | |
587 | if (s < endp && *format == '\0') { | |
588 | *s = '\0'; | |
589 | return (s - start); | |
590 | } else | |
591 | return 0; | |
592 | } | |
593 | ||
594 | /* isleap --- is a year a leap year? */ | |
595 | ||
596 | static int | |
597 | isleap(int year) | |
598 | { | |
599 | return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); | |
600 | } | |
601 | ||
602 | ||
603 | /* iso8601wknum --- compute week number according to ISO 8601 */ | |
604 | ||
605 | static int | |
606 | iso8601wknum(const struct tm *timeptr) | |
607 | { | |
608 | /* | |
609 | * From 1003.2: | |
610 | * If the week (Monday to Sunday) containing January 1 | |
611 | * has four or more days in the new year, then it is week 1; | |
612 | * otherwise it is the highest numbered week of the previous | |
613 | * year (52 or 53), and the next week is week 1. | |
614 | * | |
615 | * ADR: This means if Jan 1 was Monday through Thursday, | |
616 | * it was week 1, otherwise week 52 or 53. | |
617 | * | |
618 | * XPG4 erroneously included POSIX.2 rationale text in the | |
619 | * main body of the standard. Thus it requires week 53. | |
620 | */ | |
621 | ||
622 | int weeknum, jan1day, diff; | |
623 | ||
624 | /* get week number, Monday as first day of the week */ | |
625 | weeknum = weeknumber(timeptr, 1); | |
626 | ||
627 | /* | |
628 | * With thanks and tip of the hatlo to tml@tik.vtt.fi | |
629 | * | |
630 | * What day of the week does January 1 fall on? | |
631 | * We know that | |
632 | * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == | |
633 | * (timeptr->tm_wday - jan1.tm_wday) MOD 7 | |
634 | * and that | |
635 | * jan1.tm_yday == 0 | |
636 | * and that | |
637 | * timeptr->tm_wday MOD 7 == timeptr->tm_wday | |
638 | * from which it follows that. . . | |
639 | */ | |
640 | jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); | |
641 | if (jan1day < 0) | |
642 | jan1day += 7; | |
643 | ||
644 | /* | |
645 | * If Jan 1 was a Monday through Thursday, it was in | |
646 | * week 1. Otherwise it was last year's highest week, which is | |
647 | * this year's week 0. | |
648 | * | |
649 | * What does that mean? | |
650 | * If Jan 1 was Monday, the week number is exactly right, it can | |
651 | * never be 0. | |
652 | * If it was Tuesday through Thursday, the weeknumber is one | |
653 | * less than it should be, so we add one. | |
654 | * Otherwise, Friday, Saturday or Sunday, the week number is | |
655 | * OK, but if it is 0, it needs to be 52 or 53. | |
656 | */ | |
657 | switch (jan1day) { | |
658 | case 1: /* Monday */ | |
659 | break; | |
660 | case 2: /* Tuesday */ | |
661 | case 3: /* Wednesday */ | |
662 | case 4: /* Thursday */ | |
663 | weeknum++; | |
664 | break; | |
665 | case 5: /* Friday */ | |
666 | case 6: /* Saturday */ | |
667 | case 0: /* Sunday */ | |
668 | if (weeknum == 0) { | |
669 | #ifdef USE_BROKEN_XPG4 | |
670 | /* XPG4 (as of March 1994) says 53 unconditionally */ | |
671 | weeknum = 53; | |
672 | #else | |
673 | /* get week number of last week of last year */ | |
674 | struct tm dec31ly; /* 12/31 last year */ | |
675 | dec31ly = *timeptr; | |
676 | dec31ly.tm_year--; | |
677 | dec31ly.tm_mon = 11; | |
678 | dec31ly.tm_mday = 31; | |
679 | dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1; | |
680 | dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900); | |
681 | weeknum = iso8601wknum(& dec31ly); | |
682 | #endif | |
683 | } | |
684 | break; | |
685 | } | |
686 | ||
687 | if (timeptr->tm_mon == 11) { | |
688 | /* | |
689 | * The last week of the year | |
690 | * can be in week 1 of next year. | |
691 | * Sigh. | |
692 | * | |
693 | * This can only happen if | |
694 | * M T W | |
695 | * 29 30 31 | |
696 | * 30 31 | |
697 | * 31 | |
698 | */ | |
699 | int wday, mday; | |
700 | ||
701 | wday = timeptr->tm_wday; | |
702 | mday = timeptr->tm_mday; | |
703 | if ( (wday == 1 && (mday >= 29 && mday <= 31)) | |
704 | || (wday == 2 && (mday == 30 || mday == 31)) | |
705 | || (wday == 3 && mday == 31)) | |
706 | weeknum = 1; | |
707 | } | |
708 | ||
709 | return weeknum; | |
710 | } | |
711 | ||
712 | /* weeknumber --- figure how many weeks into the year */ | |
713 | ||
714 | /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ | |
715 | ||
716 | static int | |
717 | weeknumber(const struct tm *timeptr, int firstweekday) | |
718 | { | |
719 | int wday = timeptr->tm_wday; | |
720 | int ret; | |
721 | ||
722 | if (firstweekday == 1) { | |
723 | if (wday == 0) /* sunday */ | |
724 | wday = 6; | |
725 | else | |
726 | wday--; | |
727 | } | |
728 | ret = ((timeptr->tm_yday + 7 - wday) / 7); | |
729 | if (ret < 0) | |
730 | ret = 0; | |
731 | return ret; | |
732 | } | |
733 | ||
734 | #if 0 | |
735 | /* ADR --- I'm loathe to mess with ado's code ... */ | |
736 | ||
737 | Date: Wed, 24 Apr 91 20:54:08 MDT | |
738 | From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK> | |
739 | To: arnold@audiofax.com | |
740 | ||
741 | Hi Arnold, | |
742 | in a process of fixing of strftime() in libraries on Atari ST I grabbed | |
743 | some pieces of code from your own strftime. When doing that it came | |
744 | to mind that your weeknumber() function compiles a little bit nicer | |
745 | in the following form: | |
746 | /* | |
747 | * firstweekday is 0 if starting in Sunday, non-zero if in Monday | |
748 | */ | |
749 | { | |
750 | return (timeptr->tm_yday - timeptr->tm_wday + | |
751 | (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7; | |
752 | } | |
753 | How nicer it depends on a compiler, of course, but always a tiny bit. | |
754 | ||
755 | Cheers, | |
756 | Michal | |
757 | ntomczak@vm.ucs.ualberta.ca | |
758 | #endif | |
759 | ||
760 | #ifdef TEST_STRFTIME | |
761 | ||
762 | /* | |
763 | * NAME: | |
764 | * tst | |
765 | * | |
766 | * SYNOPSIS: | |
767 | * tst | |
768 | * | |
769 | * DESCRIPTION: | |
770 | * "tst" is a test driver for the function "strftime". | |
771 | * | |
772 | * OPTIONS: | |
773 | * None. | |
774 | * | |
775 | * AUTHOR: | |
776 | * Karl Vogel | |
777 | * Control Data Systems, Inc. | |
778 | * vogelke@c-17igp.wpafb.af.mil | |
779 | * | |
780 | * BUGS: | |
781 | * None noticed yet. | |
782 | * | |
783 | * COMPILE: | |
784 | * cc -o tst -DTEST_STRFTIME strftime.c | |
785 | */ | |
786 | ||
787 | /* ADR: I reformatted this to my liking, and deleted some unneeded code. */ | |
788 | ||
789 | #ifndef NULL | |
790 | #include <stdio.h> | |
791 | #endif | |
792 | #include <sys/time.h> | |
793 | #include <string.h> | |
794 | ||
795 | #define MAXTIME 132 | |
796 | ||
797 | /* | |
798 | * Array of time formats. | |
799 | */ | |
800 | ||
801 | static char *array[] = | |
802 | { | |
803 | "(%%A) full weekday name, var length (Sunday..Saturday) %A", | |
804 | "(%%B) full month name, var length (January..December) %B", | |
805 | "(%%C) Century %C", | |
806 | "(%%D) date (%%m/%%d/%%y) %D", | |
807 | "(%%E) Locale extensions (ignored) %E", | |
808 | "(%%F) full month name, var length (January..December) %F", | |
809 | "(%%H) hour (24-hour clock, 00..23) %H", | |
810 | "(%%I) hour (12-hour clock, 01..12) %I", | |
811 | "(%%M) minute (00..59) %M", | |
812 | "(%%N) Emporer/Era Name %N", | |
813 | "(%%O) Locale extensions (ignored) %O", | |
814 | "(%%R) time, 24-hour (%%H:%%M) %R", | |
815 | "(%%S) second (00..60) %S", | |
816 | "(%%T) time, 24-hour (%%H:%%M:%%S) %T", | |
817 | "(%%U) week of year, Sunday as first day of week (00..53) %U", | |
818 | "(%%V) week of year according to ISO 8601 %V", | |
819 | "(%%W) week of year, Monday as first day of week (00..53) %W", | |
820 | "(%%X) appropriate locale time representation (%H:%M:%S) %X", | |
821 | "(%%Y) year with century (1970...) %Y", | |
822 | "(%%Z) timezone (EDT), or blank if timezone not determinable %Z", | |
823 | "(%%a) locale's abbreviated weekday name (Sun..Sat) %a", | |
824 | "(%%b) locale's abbreviated month name (Jan..Dec) %b", | |
825 | "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c", | |
826 | "(%%d) day of the month (01..31) %d", | |
827 | "(%%e) day of the month, blank-padded ( 1..31) %e", | |
828 | "(%%h) should be same as (%%b) %h", | |
829 | "(%%j) day of the year (001..366) %j", | |
830 | "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k", | |
831 | "(%%l) hour, 12-hour clock, blank pad ( 0..12) %l", | |
832 | "(%%m) month (01..12) %m", | |
833 | "(%%o) Emporer/Era Year %o", | |
834 | "(%%p) locale's AM or PM based on 12-hour clock %p", | |
835 | "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r", | |
836 | "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u", | |
837 | "(%%v) VMS date (dd-bbb-YYYY) %v", | |
838 | "(%%w) day of week (0..6, Sunday == 0) %w", | |
839 | "(%%x) appropriate locale date representation %x", | |
840 | "(%%y) last two digits of year (00..99) %y", | |
841 | "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z", | |
842 | (char *) NULL | |
843 | }; | |
844 | ||
845 | /* main routine. */ | |
846 | ||
847 | int | |
848 | main(argc, argv) | |
849 | int argc; | |
850 | char **argv; | |
851 | { | |
852 | long time(); | |
853 | ||
854 | char *next; | |
855 | char string[MAXTIME]; | |
856 | ||
857 | int k; | |
858 | int length; | |
859 | ||
860 | struct tm *tm; | |
861 | ||
862 | long clock; | |
863 | ||
864 | /* Call the function. */ | |
865 | ||
866 | clock = time((long *) 0); | |
867 | tm = localtime(&clock); | |
868 | ||
869 | for (k = 0; next = array[k]; k++) { | |
870 | length = strftime(string, MAXTIME, next, tm); | |
871 | printf("%s\n", string); | |
872 | } | |
873 | ||
874 | exit(0); | |
875 | } | |
876 | #endif /* TEST_STRFTIME */ |