|
Ruby 1.9.2p180(2011-02-18revision30909)
|
00001 /* -*- c-file-style: "linux" -*- */ 00002 00003 /* 00004 * strftime.c 00005 * 00006 * Public-domain implementation of ANSI C library routine. 00007 * 00008 * It's written in old-style C for maximal portability. 00009 * However, since I'm used to prototypes, I've included them too. 00010 * 00011 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define. 00012 * For extensions from SunOS, add SUNOS_EXT. 00013 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE. 00014 * For VMS dates, add VMS_EXT. 00015 * For a an RFC822 time format, add MAILHEADER_EXT. 00016 * For ISO week years, add ISO_DATE_EXT. 00017 * For complete POSIX semantics, add POSIX_SEMANTICS. 00018 * 00019 * The code for %c, %x, and %X now follows the 1003.2 specification for 00020 * the POSIX locale. 00021 * This version ignores LOCALE information. 00022 * It also doesn't worry about multi-byte characters. 00023 * So there. 00024 * 00025 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of 00026 * code are included if GAWK is defined. 00027 * 00028 * Arnold Robbins 00029 * January, February, March, 1991 00030 * Updated March, April 1992 00031 * Updated April, 1993 00032 * Updated February, 1994 00033 * Updated May, 1994 00034 * Updated January, 1995 00035 * Updated September, 1995 00036 * Updated January, 1996 00037 * 00038 * Fixes from ado@elsie.nci.nih.gov 00039 * February 1991, May 1992 00040 * Fixes from Tor Lillqvist tml@tik.vtt.fi 00041 * May, 1993 00042 * Further fixes from ado@elsie.nci.nih.gov 00043 * February 1994 00044 * %z code from chip@chinacat.unicom.com 00045 * Applied September 1995 00046 * %V code fixed (again) and %G, %g added, 00047 * January 1996 00048 */ 00049 00050 #include "ruby/ruby.h" 00051 #include "timev.h" 00052 00053 #ifndef GAWK 00054 #include <stdio.h> 00055 #include <ctype.h> 00056 #include <string.h> 00057 #include <time.h> 00058 #include <sys/types.h> 00059 #include <errno.h> 00060 #endif 00061 #if defined(TM_IN_SYS_TIME) || !defined(GAWK) 00062 #include <sys/types.h> 00063 #if HAVE_SYS_TIME_H 00064 #include <sys/time.h> 00065 #endif 00066 #endif 00067 #include <math.h> 00068 00069 /* defaults: season to taste */ 00070 #define SYSV_EXT 1 /* stuff in System V ascftime routine */ 00071 #define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ 00072 #define POSIX2_DATE 1 /* stuff in Posix 1003.2 date command */ 00073 #define VMS_EXT 1 /* include %v for VMS date format */ 00074 #define MAILHEADER_EXT 1 /* add %z for HHMM format */ 00075 #define ISO_DATE_EXT 1 /* %G and %g for year of ISO week */ 00076 #ifndef GAWK 00077 #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ 00078 #endif 00079 00080 #if defined(ISO_DATE_EXT) 00081 #if ! defined(POSIX2_DATE) 00082 #define POSIX2_DATE 1 00083 #endif 00084 #endif 00085 00086 #if defined(POSIX2_DATE) 00087 #if ! defined(SYSV_EXT) 00088 #define SYSV_EXT 1 00089 #endif 00090 #if ! defined(SUNOS_EXT) 00091 #define SUNOS_EXT 1 00092 #endif 00093 #endif 00094 00095 #if defined(POSIX2_DATE) 00096 #define adddecl(stuff) stuff 00097 #else 00098 #define adddecl(stuff) 00099 #endif 00100 00101 #undef strchr /* avoid AIX weirdness */ 00102 00103 #if !defined __STDC__ && !defined _WIN32 00104 #define const 00105 static int weeknumber(); 00106 adddecl(static int iso8601wknum();) 00107 static int weeknumber_v(); 00108 adddecl(static int iso8601wknum_v();) 00109 #else 00110 static int weeknumber(const struct tm *timeptr, int firstweekday); 00111 adddecl(static int iso8601wknum(const struct tm *timeptr);) 00112 static int weeknumber_v(const struct vtm *vtm, int firstweekday); 00113 adddecl(static int iso8601wknum_v(const struct vtm *vtm);) 00114 #endif 00115 00116 #ifdef STDC_HEADERS 00117 #include <stdlib.h> 00118 #include <string.h> 00119 #else 00120 extern void *malloc(); 00121 extern void *realloc(); 00122 extern char *getenv(); 00123 extern char *strchr(); 00124 #endif 00125 00126 #define range(low, item, hi) max(low, min(item, hi)) 00127 00128 #if defined __WIN32__ || defined _WIN32 00129 #define DLL_IMPORT __declspec(dllimport) 00130 #endif 00131 #ifndef DLL_IMPORT 00132 #define DLL_IMPORT 00133 #endif 00134 #if !defined(OS2) && defined(HAVE_TZNAME) 00135 extern DLL_IMPORT char *tzname[2]; 00136 #ifdef HAVE_DAYLIGHT 00137 extern DLL_IMPORT int daylight; 00138 #endif 00139 #ifdef HAVE_VAR_TIMEZONE 00140 extern DLL_IMPORT TYPEOF_VAR_TIMEZONE timezone; 00141 #endif 00142 #ifdef HAVE_VAR_ALTZONE 00143 extern DLL_IMPORT TYPEOF_VAR_ALTZONE altzone; 00144 #endif 00145 #endif 00146 00147 #undef min /* just in case */ 00148 00149 /* min --- return minimum of two numbers */ 00150 00151 #ifndef __STDC__ 00152 static inline int 00153 min(a, b) 00154 int a, b; 00155 #else 00156 static inline int 00157 min(int a, int b) 00158 #endif 00159 { 00160 return (a < b ? a : b); 00161 } 00162 00163 #undef max /* also, just in case */ 00164 00165 /* max --- return maximum of two numbers */ 00166 00167 #ifndef __STDC__ 00168 static inline int 00169 max(a, b) 00170 int a, b; 00171 #else 00172 static inline int 00173 max(int a, int b) 00174 #endif 00175 { 00176 return (a > b ? a : b); 00177 } 00178 00179 #ifdef NO_STRING_LITERAL_CONCATENATION 00180 #error No string literal concatenation 00181 #endif 00182 00183 #define add(x,y) (rb_funcall((x), '+', 1, (y))) 00184 #define sub(x,y) (rb_funcall((x), '-', 1, (y))) 00185 #define mul(x,y) (rb_funcall((x), '*', 1, (y))) 00186 #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) 00187 #define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) 00188 #define mod(x,y) (rb_funcall((x), '%', 1, (y))) 00189 00190 /* strftime --- produce formatted time */ 00191 00192 static size_t 00193 rb_strftime_with_timespec(char *s, size_t maxsize, const char *format, const struct vtm *vtm, VALUE timev, struct timespec *ts, int gmt) 00194 { 00195 char *endp = s + maxsize; 00196 char *start = s; 00197 const char *sp, *tp; 00198 auto char tbuf[100]; 00199 long off; 00200 ptrdiff_t i; 00201 int w; 00202 long y; 00203 static short first = 1; 00204 #ifdef POSIX_SEMANTICS 00205 static char *savetz = NULL; 00206 static size_t savetzlen = 0; 00207 char *tz; 00208 #endif /* POSIX_SEMANTICS */ 00209 #ifndef HAVE_TM_ZONE 00210 #ifndef HAVE_TM_NAME 00211 #if ((defined(MAILHEADER_EXT) && !HAVE_VAR_TIMEZONE && HAVE_GETTIMEOFDAY) || \ 00212 (!HAVE_TZNAME && HAVE_TIMEZONE)) 00213 struct timeval tv; 00214 struct timezone zone; 00215 #endif 00216 #endif /* HAVE_TM_NAME */ 00217 #endif /* HAVE_TM_ZONE */ 00218 int precision, flags; 00219 char padding; 00220 enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E}; 00221 #define BIT_OF(n) (1U<<(n)) 00222 00223 /* various tables, useful in North America */ 00224 static const char days_l[][10] = { 00225 "Sunday", "Monday", "Tuesday", "Wednesday", 00226 "Thursday", "Friday", "Saturday", 00227 }; 00228 static const char months_l[][10] = { 00229 "January", "February", "March", "April", 00230 "May", "June", "July", "August", "September", 00231 "October", "November", "December", 00232 }; 00233 static const char ampm[][3] = { "AM", "PM", }; 00234 00235 if (s == NULL || format == NULL || vtm == NULL || maxsize == 0) 00236 return 0; 00237 00238 /* quick check if we even need to bother */ 00239 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { 00240 err: 00241 errno = ERANGE; 00242 return 0; 00243 } 00244 00245 #ifndef POSIX_SEMANTICS 00246 if (first) { 00247 tzset(); 00248 first = 0; 00249 } 00250 #else /* POSIX_SEMANTICS */ 00251 tz = getenv("TZ"); 00252 if (first) { 00253 if (tz != NULL) { 00254 size_t tzlen = strlen(tz); 00255 00256 savetz = (char *) malloc(tzlen + 1); 00257 if (savetz != NULL) { 00258 savetzlen = tzlen + 1; 00259 memcpy(savetz, tz, savetzlen); 00260 } 00261 } 00262 tzset(); 00263 first = 0; 00264 } 00265 /* if we have a saved TZ, and it is different, recapture and reset */ 00266 if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { 00267 size_t i = strlen(tz) + 1; 00268 if (i > savetzlen) { 00269 savetz = (char *) realloc(savetz, i); 00270 if (savetz) { 00271 savetzlen = i; 00272 memcpy(savetz, tz, i); 00273 } 00274 } else 00275 memcpy(savetz, tz, i); 00276 tzset(); 00277 } 00278 #endif /* POSIX_SEMANTICS */ 00279 00280 for (; *format && s < endp - 1; format++) { 00281 #define FLAG_FOUND() do { \ 00282 if (precision > 0 || flags & (BIT_OF(LOCALE_E)|BIT_OF(LOCALE_O))) \ 00283 goto unknown; \ 00284 } while (0) 00285 #define NEEDS(n) do if (s + (n) >= endp - 1) goto err; while (0) 00286 #define FILL_PADDING(i) do { \ 00287 if (!(flags & BIT_OF(LEFT)) && precision > i) { \ 00288 NEEDS(precision); \ 00289 memset(s, padding ? padding : ' ', precision - i); \ 00290 s += precision - i; \ 00291 } \ 00292 else { \ 00293 NEEDS(i); \ 00294 } \ 00295 } while (0); 00296 #define FMT(def_pad, def_prec, fmt, val) \ 00297 do { \ 00298 int l; \ 00299 if (precision <= 0) precision = (def_prec); \ 00300 if (flags & BIT_OF(LEFT)) precision = 1; \ 00301 l = snprintf(s, endp - s, \ 00302 ((padding == '0' || (!padding && def_pad == '0')) ? "%0*"fmt : "%*"fmt), \ 00303 precision, val); \ 00304 if (l < 0) goto err; \ 00305 s += l; \ 00306 } while (0) 00307 #define STRFTIME(fmt) \ 00308 do { \ 00309 i = rb_strftime_with_timespec(s, endp - s, fmt, vtm, timev, ts, gmt); \ 00310 if (!i) return 0; \ 00311 if (precision > i) {\ 00312 memmove(s + precision - i, s, i);\ 00313 memset(s, padding ? padding : ' ', precision - i); \ 00314 s += precision; \ 00315 }\ 00316 else s += i; \ 00317 } while (0) 00318 #define FMTV(def_pad, def_prec, fmt, val) \ 00319 do { \ 00320 VALUE tmp = (val); \ 00321 if (FIXNUM_P(tmp)) { \ 00322 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ 00323 } \ 00324 else { \ 00325 VALUE args[2], result; \ 00326 size_t l; \ 00327 if (precision <= 0) precision = (def_prec); \ 00328 if (flags & BIT_OF(LEFT)) precision = 1; \ 00329 args[0] = INT2FIX(precision); \ 00330 args[1] = val; \ 00331 if (padding == '0' || (!padding && def_pad == '0')) \ 00332 result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ 00333 else \ 00334 result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ 00335 l = strlcpy(s, StringValueCStr(result), endp-s); \ 00336 if ((size_t)(endp-s) <= l) \ 00337 goto err; \ 00338 s += l; \ 00339 } \ 00340 } while (0) 00341 00342 if (*format != '%') { 00343 *s++ = *format; 00344 continue; 00345 } 00346 tp = tbuf; 00347 sp = format; 00348 precision = -1; 00349 flags = 0; 00350 padding = 0; 00351 again: 00352 switch (*++format) { 00353 case '\0': 00354 format--; 00355 goto unknown; 00356 00357 case '%': 00358 FILL_PADDING(1); 00359 *s++ = '%'; 00360 continue; 00361 00362 case 'a': /* abbreviated weekday name */ 00363 if (flags & BIT_OF(CHCASE)) { 00364 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 00365 flags |= BIT_OF(UPPER); 00366 } 00367 if (vtm->wday < 0 || vtm->wday > 6) 00368 i = 1, tp = "?"; 00369 else 00370 i = 3, tp = days_l[vtm->wday]; 00371 break; 00372 00373 case 'A': /* full weekday name */ 00374 if (flags & BIT_OF(CHCASE)) { 00375 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 00376 flags |= BIT_OF(UPPER); 00377 } 00378 if (vtm->wday < 0 || vtm->wday > 6) 00379 i = 1, tp = "?"; 00380 else 00381 i = strlen(tp = days_l[vtm->wday]); 00382 break; 00383 00384 #ifdef SYSV_EXT 00385 case 'h': /* abbreviated month name */ 00386 #endif 00387 case 'b': /* abbreviated month name */ 00388 if (flags & BIT_OF(CHCASE)) { 00389 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 00390 flags |= BIT_OF(UPPER); 00391 } 00392 if (vtm->mon < 1 || vtm->mon > 12) 00393 i = 1, tp = "?"; 00394 else 00395 i = 3, tp = months_l[vtm->mon-1]; 00396 break; 00397 00398 case 'B': /* full month name */ 00399 if (flags & BIT_OF(CHCASE)) { 00400 flags &= ~(BIT_OF(LOWER)|BIT_OF(CHCASE)); 00401 flags |= BIT_OF(UPPER); 00402 } 00403 if (vtm->mon < 1 || vtm->mon > 12) 00404 i = 1, tp = "?"; 00405 else 00406 i = strlen(tp = months_l[vtm->mon-1]); 00407 break; 00408 00409 case 'c': /* appropriate date and time representation */ 00410 STRFTIME("%a %b %e %H:%M:%S %Y"); 00411 continue; 00412 00413 case 'd': /* day of the month, 01 - 31 */ 00414 i = range(1, vtm->mday, 31); 00415 FMT('0', 2, "d", (int)i); 00416 continue; 00417 00418 case 'H': /* hour, 24-hour clock, 00 - 23 */ 00419 i = range(0, vtm->hour, 23); 00420 FMT('0', 2, "d", (int)i); 00421 continue; 00422 00423 case 'I': /* hour, 12-hour clock, 01 - 12 */ 00424 i = range(0, vtm->hour, 23); 00425 if (i == 0) 00426 i = 12; 00427 else if (i > 12) 00428 i -= 12; 00429 FMT('0', 2, "d", (int)i); 00430 continue; 00431 00432 case 'j': /* day of the year, 001 - 366 */ 00433 FMT('0', 3, "d", vtm->yday); 00434 continue; 00435 00436 case 'm': /* month, 01 - 12 */ 00437 i = range(1, vtm->mon, 12); 00438 FMT('0', 2, "d", (int)i); 00439 continue; 00440 00441 case 'M': /* minute, 00 - 59 */ 00442 i = range(0, vtm->min, 59); 00443 FMT('0', 2, "d", (int)i); 00444 continue; 00445 00446 case 'p': /* AM or PM based on 12-hour clock */ 00447 case 'P': /* am or pm based on 12-hour clock */ 00448 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || 00449 (*format == 'P' && !(flags & (BIT_OF(CHCASE)|BIT_OF(UPPER))))) { 00450 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE)); 00451 flags |= BIT_OF(LOWER); 00452 } 00453 i = range(0, vtm->hour, 23); 00454 if (i < 12) 00455 tp = ampm[0]; 00456 else 00457 tp = ampm[1]; 00458 i = 2; 00459 break; 00460 00461 case 's': 00462 if (ts) { 00463 time_t sec = ts->tv_sec; 00464 if (~(time_t)0 <= 0) 00465 FMT('0', 1, PRI_TIMET_PREFIX"d", sec); 00466 else 00467 FMT('0', 1, PRI_TIMET_PREFIX"u", sec); 00468 } 00469 else { 00470 VALUE sec = div(timev, INT2FIX(1)); 00471 FMTV('0', 1, "d", sec); 00472 } 00473 continue; 00474 00475 case 'S': /* second, 00 - 60 */ 00476 i = range(0, vtm->sec, 60); 00477 FMT('0', 2, "d", (int)i); 00478 continue; 00479 00480 case 'U': /* week of year, Sunday is first day of week */ 00481 FMT('0', 2, "d", weeknumber_v(vtm, 0)); 00482 continue; 00483 00484 case 'w': /* weekday, Sunday == 0, 0 - 6 */ 00485 i = range(0, vtm->wday, 6); 00486 FMT('0', 1, "d", (int)i); 00487 continue; 00488 00489 case 'W': /* week of year, Monday is first day of week */ 00490 FMT('0', 2, "d", weeknumber_v(vtm, 1)); 00491 continue; 00492 00493 case 'x': /* appropriate date representation */ 00494 STRFTIME("%m/%d/%y"); 00495 continue; 00496 00497 case 'X': /* appropriate time representation */ 00498 STRFTIME("%H:%M:%S"); 00499 continue; 00500 00501 case 'y': /* year without a century, 00 - 99 */ 00502 i = NUM2INT(mod(vtm->year, INT2FIX(100))); 00503 FMT('0', 2, "d", (int)i); 00504 continue; 00505 00506 case 'Y': /* year with century */ 00507 if (FIXNUM_P(vtm->year)) { 00508 long y = FIX2LONG(vtm->year); 00509 FMT('0', 0 <= y ? 4 : 5, "ld", y); 00510 } 00511 else { 00512 FMTV('0', 4, "d", vtm->year); 00513 } 00514 continue; 00515 00516 #ifdef MAILHEADER_EXT 00517 /* 00518 * From: Chip Rosenthal <chip@chinacat.unicom.com> 00519 * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST) 00520 * 00521 * Warning: the %z [code] is implemented by inspecting the 00522 * timezone name conditional compile settings, and 00523 * inferring a method to get timezone offsets. I've tried 00524 * this code on a couple of machines, but I don't doubt 00525 * there is some system out there that won't like it. 00526 * Maybe the easiest thing to do would be to bracket this 00527 * with an #ifdef that can turn it off. The %z feature 00528 * would be an admittedly obscure one that most folks can 00529 * live without, but it would be a great help to those of 00530 * us that muck around with various message processors. 00531 */ 00532 case 'z': /* time zone offset east of GMT e.g. -0600 */ 00533 if (precision < 4) precision = 4; 00534 NEEDS(precision + 1); 00535 if (gmt) { 00536 off = 0; 00537 } 00538 else { 00539 off = NUM2LONG(rb_funcall(quo(vtm->utc_offset, INT2FIX(60)), rb_intern("round"), 0)); 00540 #if 0 00541 #ifdef HAVE_TM_NAME 00542 /* 00543 * Systems with tm_name probably have tm_tzadj as 00544 * secs west of GMT. Convert to mins east of GMT. 00545 */ 00546 off = -timeptr->tm_tzadj / 60; 00547 #else /* !HAVE_TM_NAME */ 00548 #ifdef HAVE_TM_ZONE 00549 /* 00550 * Systems with tm_zone probably have tm_gmtoff as 00551 * secs east of GMT. Convert to mins east of GMT. 00552 */ 00553 off = timeptr->tm_gmtoff / 60; 00554 #else /* !HAVE_TM_ZONE */ 00555 #if HAVE_VAR_TIMEZONE 00556 #if HAVE_VAR_ALTZONE 00557 off = -(daylight ? timezone : altzone) / 60; 00558 #else 00559 off = -timezone / 60; 00560 #endif 00561 #else /* !HAVE_VAR_TIMEZONE */ 00562 #ifdef HAVE_GETTIMEOFDAY 00563 gettimeofday(&tv, &zone); 00564 off = -zone.tz_minuteswest; 00565 #else 00566 /* no timezone info, then calc by myself */ 00567 { 00568 struct tm utc; 00569 time_t now; 00570 time(&now); 00571 utc = *gmtime(&now); 00572 off = (long)((now - mktime(&utc)) / 60); 00573 } 00574 #endif 00575 #endif /* !HAVE_VAR_TIMEZONE */ 00576 #endif /* !HAVE_TM_ZONE */ 00577 #endif /* !HAVE_TM_NAME */ 00578 #endif /* 0 */ 00579 } 00580 if (off < 0) { 00581 off = -off; 00582 *s++ = '-'; 00583 } else { 00584 *s++ = '+'; 00585 } 00586 off = off/60*100 + off%60; 00587 i = snprintf(s, endp - s, (padding == ' ' ? "%*ld" : "%.*ld"), 00588 precision - (precision > 4), off); 00589 if (i < 0) goto err; 00590 s += i; 00591 continue; 00592 #endif /* MAILHEADER_EXT */ 00593 00594 case 'Z': /* time zone name or abbreviation */ 00595 if (flags & BIT_OF(CHCASE)) { 00596 flags &= ~(BIT_OF(UPPER)|BIT_OF(CHCASE)); 00597 flags |= BIT_OF(LOWER); 00598 } 00599 if (gmt) { 00600 i = 3; 00601 tp = "UTC"; 00602 break; 00603 } 00604 #if 0 00605 #ifdef HAVE_TZNAME 00606 i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */ 00607 tp = tzname[i]; 00608 #else 00609 #ifdef HAVE_TM_ZONE 00610 tp = timeptr->tm_zone; 00611 #else 00612 #ifdef HAVE_TM_NAME 00613 tp = timeptr->tm_name; 00614 #else 00615 #ifdef HAVE_TIMEZONE 00616 gettimeofday(& tv, & zone); 00617 #ifdef TIMEZONE_VOID 00618 tp = timezone(); 00619 #else 00620 tp = timezone(zone.tz_minuteswest, timeptr->tm_isdst > 0); 00621 #endif /* TIMEZONE_VOID */ 00622 #endif /* HAVE_TIMEZONE */ 00623 #endif /* HAVE_TM_NAME */ 00624 #endif /* HAVE_TM_ZONE */ 00625 #endif /* HAVE_TZNAME */ 00626 #endif /* 0 */ 00627 if (vtm->zone == NULL) 00628 tp = ""; 00629 else 00630 tp = vtm->zone; 00631 i = strlen(tp); 00632 break; 00633 00634 #ifdef SYSV_EXT 00635 case 'n': /* same as \n */ 00636 FILL_PADDING(1); 00637 *s++ = '\n'; 00638 continue; 00639 00640 case 't': /* same as \t */ 00641 FILL_PADDING(1); 00642 *s++ = '\t'; 00643 continue; 00644 00645 case 'D': /* date as %m/%d/%y */ 00646 STRFTIME("%m/%d/%y"); 00647 continue; 00648 00649 case 'e': /* day of month, blank padded */ 00650 FMT(' ', 2, "d", range(1, vtm->mday, 31)); 00651 continue; 00652 00653 case 'r': /* time as %I:%M:%S %p */ 00654 STRFTIME("%I:%M:%S %p"); 00655 continue; 00656 00657 case 'R': /* time as %H:%M */ 00658 STRFTIME("%H:%M"); 00659 continue; 00660 00661 case 'T': /* time as %H:%M:%S */ 00662 STRFTIME("%H:%M:%S"); 00663 continue; 00664 #endif 00665 00666 #ifdef SUNOS_EXT 00667 case 'k': /* hour, 24-hour clock, blank pad */ 00668 i = range(0, vtm->hour, 23); 00669 FMT(' ', 2, "d", (int)i); 00670 continue; 00671 00672 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ 00673 i = range(0, vtm->hour, 23); 00674 if (i == 0) 00675 i = 12; 00676 else if (i > 12) 00677 i -= 12; 00678 FMT(' ', 2, "d", (int)i); 00679 continue; 00680 #endif 00681 00682 00683 #ifdef VMS_EXT 00684 case 'v': /* date as dd-bbb-YYYY */ 00685 STRFTIME("%e-%^b-%4Y"); 00686 continue; 00687 #endif 00688 00689 00690 #ifdef POSIX2_DATE 00691 case 'C': 00692 FMTV('0', 2, "d", div(vtm->year, INT2FIX(100))); 00693 continue; 00694 00695 case 'E': 00696 /* POSIX locale extensions, ignored for now */ 00697 flags |= BIT_OF(LOCALE_E); 00698 goto again; 00699 case 'O': 00700 /* POSIX locale extensions, ignored for now */ 00701 flags |= BIT_OF(LOCALE_O); 00702 goto again; 00703 00704 case 'V': /* week of year according ISO 8601 */ 00705 FMT('0', 2, "d", iso8601wknum_v(vtm)); 00706 continue; 00707 00708 case 'u': 00709 /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ 00710 FMT('0', 1, "d", vtm->wday == 0 ? 7 : vtm->wday); 00711 continue; 00712 #endif /* POSIX2_DATE */ 00713 00714 #ifdef ISO_DATE_EXT 00715 case 'G': 00716 case 'g': 00717 /* 00718 * Year of ISO week. 00719 * 00720 * If it's December but the ISO week number is one, 00721 * that week is in next year. 00722 * If it's January but the ISO week number is 52 or 00723 * 53, that week is in last year. 00724 * Otherwise, it's this year. 00725 */ 00726 { 00727 VALUE yv = vtm->year; 00728 w = iso8601wknum_v(vtm); 00729 if (vtm->mon == 12 && w == 1) 00730 yv = add(yv, INT2FIX(1)); 00731 else if (vtm->mon == 1 && w >= 52) 00732 yv = sub(yv, INT2FIX(1)); 00733 00734 if (*format == 'G') { 00735 FMTV('0', 1, "d", yv); 00736 } 00737 else { 00738 yv = mod(yv, INT2FIX(100)); 00739 y = FIX2LONG(yv); 00740 FMT('0', 2, "ld", y); 00741 } 00742 continue; 00743 } 00744 00745 #endif /* ISO_DATE_EXT */ 00746 00747 00748 case 'L': 00749 w = 3; 00750 goto subsec; 00751 00752 case 'N': 00753 /* 00754 * fractional second digits. default is 9 digits 00755 * (nanosecond). 00756 * 00757 * %3N millisecond (3 digits) 00758 * %6N microsecond (6 digits) 00759 * %9N nanosecond (9 digits) 00760 */ 00761 w = 9; 00762 subsec: 00763 if (precision <= 0) { 00764 precision = w; 00765 } 00766 NEEDS(precision); 00767 00768 if (ts) { 00769 long subsec = ts->tv_nsec; 00770 if (9 < precision) { 00771 snprintf(s, endp - s, "%09ld", subsec); 00772 memset(s+9, '0', precision-9); 00773 s += precision; 00774 } 00775 else { 00776 int i; 00777 for (i = 0; i < 9-precision; i++) 00778 subsec /= 10; 00779 snprintf(s, endp - s, "%0*ld", precision, subsec); 00780 s += precision; 00781 } 00782 } 00783 else { 00784 VALUE subsec = mod(timev, INT2FIX(1)); 00785 int ww; 00786 long n; 00787 00788 ww = precision; 00789 while (9 <= ww) { 00790 subsec = mul(subsec, INT2FIX(1000000000)); 00791 ww -= 9; 00792 } 00793 n = 1; 00794 for (; 0 < ww; ww--) 00795 n *= 10; 00796 if (n != 1) 00797 subsec = mul(subsec, INT2FIX(n)); 00798 subsec = div(subsec, INT2FIX(1)); 00799 00800 if (FIXNUM_P(subsec)) { 00801 int l; 00802 l = snprintf(s, endp - s, "%0*ld", precision, FIX2LONG(subsec)); 00803 s += precision; 00804 } 00805 else { 00806 VALUE args[2], result; 00807 size_t l; 00808 args[0] = INT2FIX(precision); 00809 args[1] = subsec; 00810 result = rb_str_format(2, args, rb_str_new2("%0*d")); 00811 l = strlcpy(s, StringValueCStr(result), endp-s); 00812 s += precision; 00813 } 00814 } 00815 continue; 00816 00817 case 'F': /* Equivalent to %Y-%m-%d */ 00818 STRFTIME("%Y-%m-%d"); 00819 continue; 00820 00821 case '-': 00822 FLAG_FOUND(); 00823 flags |= BIT_OF(LEFT); 00824 padding = precision = 0; 00825 goto again; 00826 00827 case '^': 00828 FLAG_FOUND(); 00829 flags |= BIT_OF(UPPER); 00830 goto again; 00831 00832 case '#': 00833 FLAG_FOUND(); 00834 flags |= BIT_OF(CHCASE); 00835 goto again; 00836 00837 case '_': 00838 FLAG_FOUND(); 00839 padding = ' '; 00840 goto again; 00841 00842 case '0': 00843 padding = '0'; 00844 case '1': case '2': case '3': case '4': 00845 case '5': case '6': case '7': case '8': case '9': 00846 { 00847 char *e; 00848 precision = (int)strtoul(format, &e, 10); 00849 format = e - 1; 00850 goto again; 00851 } 00852 00853 default: 00854 unknown: 00855 i = format - sp + 1; 00856 tp = sp; 00857 precision = -1; 00858 flags = 0; 00859 padding = 0; 00860 break; 00861 } 00862 if (i) { 00863 FILL_PADDING(i); 00864 memcpy(s, tp, i); 00865 switch (flags & (BIT_OF(UPPER)|BIT_OF(LOWER))) { 00866 case BIT_OF(UPPER): 00867 do { 00868 if (ISLOWER(*s)) *s = TOUPPER(*s); 00869 } while (s++, --i); 00870 break; 00871 case BIT_OF(LOWER): 00872 do { 00873 if (ISUPPER(*s)) *s = TOLOWER(*s); 00874 } while (s++, --i); 00875 break; 00876 default: 00877 s += i; 00878 break; 00879 } 00880 } 00881 } 00882 if (s >= endp) { 00883 goto err; 00884 } 00885 if (*format == '\0') { 00886 *s = '\0'; 00887 return (s - start); 00888 } else 00889 return 0; 00890 } 00891 00892 size_t 00893 rb_strftime(char *s, size_t maxsize, const char *format, const struct vtm *vtm, VALUE timev, int gmt) 00894 { 00895 return rb_strftime_with_timespec(s, maxsize, format, vtm, timev, NULL, gmt); 00896 } 00897 00898 size_t 00899 rb_strftime_timespec(char *s, size_t maxsize, const char *format, const struct vtm *vtm, struct timespec *ts, int gmt) 00900 { 00901 return rb_strftime_with_timespec(s, maxsize, format, vtm, Qnil, ts, gmt); 00902 } 00903 00904 /* isleap --- is a year a leap year? */ 00905 00906 #ifndef __STDC__ 00907 static int 00908 isleap(year) 00909 long year; 00910 #else 00911 static int 00912 isleap(long year) 00913 #endif 00914 { 00915 return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); 00916 } 00917 00918 00919 static void 00920 vtm2tm_noyear(const struct vtm *vtm, struct tm *result) 00921 { 00922 struct tm tm; 00923 00924 /* for isleap() in iso8601wknum. +100 is -1900 (mod 400). */ 00925 tm.tm_year = FIX2INT(mod(vtm->year, INT2FIX(400))) + 100; 00926 00927 tm.tm_mon = vtm->mon-1; 00928 tm.tm_mday = vtm->mday; 00929 tm.tm_hour = vtm->hour; 00930 tm.tm_min = vtm->min; 00931 tm.tm_sec = vtm->sec; 00932 tm.tm_wday = vtm->wday; 00933 tm.tm_yday = vtm->yday-1; 00934 tm.tm_isdst = vtm->isdst; 00935 #if defined(HAVE_STRUCT_TM_TM_GMTOFF) 00936 tm.tm_gmtoff = NUM2LONG(vtm->utc_offset); 00937 #endif 00938 #if defined(HAVE_TM_ZONE) 00939 tm.tm_zone = (char *)vtm->zone; 00940 #endif 00941 *result = tm; 00942 } 00943 00944 #ifdef POSIX2_DATE 00945 /* iso8601wknum --- compute week number according to ISO 8601 */ 00946 00947 #ifndef __STDC__ 00948 static int 00949 iso8601wknum(timeptr) 00950 const struct tm *timeptr; 00951 #else 00952 static int 00953 iso8601wknum(const struct tm *timeptr) 00954 #endif 00955 { 00956 /* 00957 * From 1003.2: 00958 * If the week (Monday to Sunday) containing January 1 00959 * has four or more days in the new year, then it is week 1; 00960 * otherwise it is the highest numbered week of the previous 00961 * year (52 or 53), and the next week is week 1. 00962 * 00963 * ADR: This means if Jan 1 was Monday through Thursday, 00964 * it was week 1, otherwise week 52 or 53. 00965 * 00966 * XPG4 erroneously included POSIX.2 rationale text in the 00967 * main body of the standard. Thus it requires week 53. 00968 */ 00969 00970 int weeknum, jan1day; 00971 00972 /* get week number, Monday as first day of the week */ 00973 weeknum = weeknumber(timeptr, 1); 00974 00975 /* 00976 * With thanks and tip of the hatlo to tml@tik.vtt.fi 00977 * 00978 * What day of the week does January 1 fall on? 00979 * We know that 00980 * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == 00981 * (timeptr->tm_wday - jan1.tm_wday) MOD 7 00982 * and that 00983 * jan1.tm_yday == 0 00984 * and that 00985 * timeptr->tm_wday MOD 7 == timeptr->tm_wday 00986 * from which it follows that. . . 00987 */ 00988 jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); 00989 if (jan1day < 0) 00990 jan1day += 7; 00991 00992 /* 00993 * If Jan 1 was a Monday through Thursday, it was in 00994 * week 1. Otherwise it was last year's highest week, which is 00995 * this year's week 0. 00996 * 00997 * What does that mean? 00998 * If Jan 1 was Monday, the week number is exactly right, it can 00999 * never be 0. 01000 * If it was Tuesday through Thursday, the weeknumber is one 01001 * less than it should be, so we add one. 01002 * Otherwise, Friday, Saturday or Sunday, the week number is 01003 * OK, but if it is 0, it needs to be 52 or 53. 01004 */ 01005 switch (jan1day) { 01006 case 1: /* Monday */ 01007 break; 01008 case 2: /* Tuesday */ 01009 case 3: /* Wednesday */ 01010 case 4: /* Thursday */ 01011 weeknum++; 01012 break; 01013 case 5: /* Friday */ 01014 case 6: /* Saturday */ 01015 case 0: /* Sunday */ 01016 if (weeknum == 0) { 01017 #ifdef USE_BROKEN_XPG4 01018 /* XPG4 (as of March 1994) says 53 unconditionally */ 01019 weeknum = 53; 01020 #else 01021 /* get week number of last week of last year */ 01022 struct tm dec31ly; /* 12/31 last year */ 01023 dec31ly = *timeptr; 01024 dec31ly.tm_year--; 01025 dec31ly.tm_mon = 11; 01026 dec31ly.tm_mday = 31; 01027 dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1; 01028 dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L); 01029 weeknum = iso8601wknum(& dec31ly); 01030 #endif 01031 } 01032 break; 01033 } 01034 01035 if (timeptr->tm_mon == 11) { 01036 /* 01037 * The last week of the year 01038 * can be in week 1 of next year. 01039 * Sigh. 01040 * 01041 * This can only happen if 01042 * M T W 01043 * 29 30 31 01044 * 30 31 01045 * 31 01046 */ 01047 int wday, mday; 01048 01049 wday = timeptr->tm_wday; 01050 mday = timeptr->tm_mday; 01051 if ( (wday == 1 && (mday >= 29 && mday <= 31)) 01052 || (wday == 2 && (mday == 30 || mday == 31)) 01053 || (wday == 3 && mday == 31)) 01054 weeknum = 1; 01055 } 01056 01057 return weeknum; 01058 } 01059 01060 static int 01061 iso8601wknum_v(const struct vtm *vtm) 01062 { 01063 struct tm tm; 01064 vtm2tm_noyear(vtm, &tm); 01065 return iso8601wknum(&tm); 01066 } 01067 01068 #endif 01069 01070 /* weeknumber --- figure how many weeks into the year */ 01071 01072 /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ 01073 01074 #ifndef __STDC__ 01075 static int 01076 weeknumber(timeptr, firstweekday) 01077 const struct tm *timeptr; 01078 int firstweekday; 01079 #else 01080 static int 01081 weeknumber(const struct tm *timeptr, int firstweekday) 01082 #endif 01083 { 01084 int wday = timeptr->tm_wday; 01085 int ret; 01086 01087 if (firstweekday == 1) { 01088 if (wday == 0) /* sunday */ 01089 wday = 6; 01090 else 01091 wday--; 01092 } 01093 ret = ((timeptr->tm_yday + 7 - wday) / 7); 01094 if (ret < 0) 01095 ret = 0; 01096 return ret; 01097 } 01098 01099 static int 01100 weeknumber_v(const struct vtm *vtm, int firstweekday) 01101 { 01102 struct tm tm; 01103 vtm2tm_noyear(vtm, &tm); 01104 return weeknumber(&tm, firstweekday); 01105 } 01106 01107 #if 0 01108 /* ADR --- I'm loathe to mess with ado's code ... */ 01109 01110 Date: Wed, 24 Apr 91 20:54:08 MDT 01111 From: Michal Jaegermann <audfax!emory!vm.ucs.UAlberta.CA!NTOMCZAK> 01112 To: arnold@audiofax.com 01113 01114 Hi Arnold, 01115 in a process of fixing of strftime() in libraries on Atari ST I grabbed 01116 some pieces of code from your own strftime. When doing that it came 01117 to mind that your weeknumber() function compiles a little bit nicer 01118 in the following form: 01119 /* 01120 * firstweekday is 0 if starting in Sunday, non-zero if in Monday 01121 */ 01122 { 01123 return (timeptr->tm_yday - timeptr->tm_wday + 01124 (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7; 01125 } 01126 How nicer it depends on a compiler, of course, but always a tiny bit. 01127 01128 Cheers, 01129 Michal 01130 ntomczak@vm.ucs.ualberta.ca 01131 #endif 01132 01133 #ifdef TEST_STRFTIME 01134 01135 /* 01136 * NAME: 01137 * tst 01138 * 01139 * SYNOPSIS: 01140 * tst 01141 * 01142 * DESCRIPTION: 01143 * "tst" is a test driver for the function "strftime". 01144 * 01145 * OPTIONS: 01146 * None. 01147 * 01148 * AUTHOR: 01149 * Karl Vogel 01150 * Control Data Systems, Inc. 01151 * vogelke@c-17igp.wpafb.af.mil 01152 * 01153 * BUGS: 01154 * None noticed yet. 01155 * 01156 * COMPILE: 01157 * cc -o tst -DTEST_STRFTIME strftime.c 01158 */ 01159 01160 /* ADR: I reformatted this to my liking, and deleted some unneeded code. */ 01161 01162 #ifndef NULL 01163 #include <stdio.h> 01164 #endif 01165 #include <sys/time.h> 01166 #include <string.h> 01167 01168 #define MAXTIME 132 01169 01170 /* 01171 * Array of time formats. 01172 */ 01173 01174 static char *array[] = 01175 { 01176 "(%%A) full weekday name, var length (Sunday..Saturday) %A", 01177 "(%%B) full month name, var length (January..December) %B", 01178 "(%%C) Century %C", 01179 "(%%D) date (%%m/%%d/%%y) %D", 01180 "(%%E) Locale extensions (ignored) %E", 01181 "(%%H) hour (24-hour clock, 00..23) %H", 01182 "(%%I) hour (12-hour clock, 01..12) %I", 01183 "(%%M) minute (00..59) %M", 01184 "(%%O) Locale extensions (ignored) %O", 01185 "(%%R) time, 24-hour (%%H:%%M) %R", 01186 "(%%S) second (00..60) %S", 01187 "(%%T) time, 24-hour (%%H:%%M:%%S) %T", 01188 "(%%U) week of year, Sunday as first day of week (00..53) %U", 01189 "(%%V) week of year according to ISO 8601 %V", 01190 "(%%W) week of year, Monday as first day of week (00..53) %W", 01191 "(%%X) appropriate locale time representation (%H:%M:%S) %X", 01192 "(%%Y) year with century (1970...) %Y", 01193 "(%%Z) timezone (EDT), or blank if timezone not determinable %Z", 01194 "(%%a) locale's abbreviated weekday name (Sun..Sat) %a", 01195 "(%%b) locale's abbreviated month name (Jan..Dec) %b", 01196 "(%%c) full date (Sat Nov 4 12:02:33 1989)%n%t%t%t %c", 01197 "(%%d) day of the month (01..31) %d", 01198 "(%%e) day of the month, blank-padded ( 1..31) %e", 01199 "(%%h) should be same as (%%b) %h", 01200 "(%%j) day of the year (001..366) %j", 01201 "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k", 01202 "(%%l) hour, 12-hour clock, blank pad ( 1..12) %l", 01203 "(%%m) month (01..12) %m", 01204 "(%%p) locale's AM or PM based on 12-hour clock %p", 01205 "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r", 01206 "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u", 01207 "(%%v) VMS date (dd-bbb-YYYY) %v", 01208 "(%%w) day of week (0..6, Sunday == 0) %w", 01209 "(%%x) appropriate locale date representation %x", 01210 "(%%y) last two digits of year (00..99) %y", 01211 "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z", 01212 (char *) NULL 01213 }; 01214 01215 /* main routine. */ 01216 01217 int 01218 main(argc, argv) 01219 int argc; 01220 char **argv; 01221 { 01222 long time(); 01223 01224 char *next; 01225 char string[MAXTIME]; 01226 01227 int k; 01228 int length; 01229 01230 struct tm *tm; 01231 01232 long clock; 01233 01234 /* Call the function. */ 01235 01236 clock = time((long *) 0); 01237 tm = localtime(&clock); 01238 01239 for (k = 0; next = array[k]; k++) { 01240 length = strftime(string, MAXTIME, next, tm); 01241 printf("%s\n", string); 01242 } 01243 01244 exit(0); 01245 } 01246 #endif /* TEST_STRFTIME */ 01247
1.7.3