Ruby
2.0.0p247(2013-06-27revision41674)
|
00001 /* 00002 date_strftime.c: based on a public-domain implementation of ANSI C 00003 library routine strftime, which is originally written by Arnold 00004 Robbins. 00005 */ 00006 00007 #include "ruby/ruby.h" 00008 #include "date_tmx.h" 00009 00010 #include <stdlib.h> 00011 #include <string.h> 00012 #include <ctype.h> 00013 #include <errno.h> 00014 00015 #if defined(HAVE_SYS_TIME_H) 00016 #include <sys/time.h> 00017 #endif 00018 00019 #undef strchr /* avoid AIX weirdness */ 00020 00021 #define range(low, item, hi) (item) 00022 00023 #define add(x,y) (rb_funcall((x), '+', 1, (y))) 00024 #define sub(x,y) (rb_funcall((x), '-', 1, (y))) 00025 #define mul(x,y) (rb_funcall((x), '*', 1, (y))) 00026 #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) 00027 #define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) 00028 #define mod(x,y) (rb_funcall((x), '%', 1, (y))) 00029 00030 static void 00031 upcase(char *s, size_t i) 00032 { 00033 do { 00034 if (ISLOWER(*s)) 00035 *s = TOUPPER(*s); 00036 } while (s++, --i); 00037 } 00038 00039 static void 00040 downcase(char *s, size_t i) 00041 { 00042 do { 00043 if (ISUPPER(*s)) 00044 *s = TOLOWER(*s); 00045 } while (s++, --i); 00046 } 00047 00048 /* strftime --- produce formatted time */ 00049 00050 static size_t 00051 date_strftime_with_tmx(char *s, size_t maxsize, const char *format, 00052 const struct tmx *tmx) 00053 { 00054 char *endp = s + maxsize; 00055 char *start = s; 00056 const char *sp, *tp; 00057 auto char tbuf[100]; 00058 ptrdiff_t i; 00059 int v, w; 00060 size_t colons; 00061 int precision, flags; 00062 char padding; 00063 /* LOCALE_[OE] and COLONS are actually modifiers, not flags */ 00064 enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS}; 00065 #define BIT_OF(n) (1U<<(n)) 00066 00067 /* various tables for locale C */ 00068 static const char days_l[][10] = { 00069 "Sunday", "Monday", "Tuesday", "Wednesday", 00070 "Thursday", "Friday", "Saturday", 00071 }; 00072 static const char months_l[][10] = { 00073 "January", "February", "March", "April", 00074 "May", "June", "July", "August", "September", 00075 "October", "November", "December", 00076 }; 00077 static const char ampm[][3] = { "AM", "PM", }; 00078 00079 if (s == NULL || format == NULL || tmx == NULL || maxsize == 0) 00080 return 0; 00081 00082 /* quick check if we even need to bother */ 00083 if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { 00084 err: 00085 errno = ERANGE; 00086 return 0; 00087 } 00088 00089 for (; *format && s < endp - 1; format++) { 00090 #define FLAG_FOUND() do { \ 00091 if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \ 00092 goto unknown; \ 00093 } while (0) 00094 #define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0) 00095 #define FILL_PADDING(i) do { \ 00096 if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \ 00097 NEEDS(precision); \ 00098 memset(s, padding ? padding : ' ', precision - (i)); \ 00099 s += precision - (i); \ 00100 } \ 00101 else { \ 00102 NEEDS(i); \ 00103 } \ 00104 } while (0); 00105 #define FMT(def_pad, def_prec, fmt, val) \ 00106 do { \ 00107 int l; \ 00108 if (precision <= 0) precision = (def_prec); \ 00109 if (flags & BIT_OF(LEFT)) precision = 1; \ 00110 l = snprintf(s, endp - s, \ 00111 ((padding == '0' || (!padding && (def_pad) == '0')) ? \ 00112 "%0*"fmt : "%*"fmt), \ 00113 precision, (val)); \ 00114 if (l < 0) goto err; \ 00115 s += l; \ 00116 } while (0) 00117 #define STRFTIME(fmt) \ 00118 do { \ 00119 i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \ 00120 if (!i) return 0; \ 00121 if (flags & BIT_OF(UPPER)) \ 00122 upcase(s, i); \ 00123 if (!(flags & BIT_OF(LEFT)) && precision > i) { \ 00124 if (start + maxsize < s + precision) { \ 00125 errno = ERANGE; \ 00126 return 0; \ 00127 } \ 00128 memmove(s + precision - i, s, i); \ 00129 memset(s, padding ? padding : ' ', precision - i); \ 00130 s += precision; \ 00131 } \ 00132 else s += i; \ 00133 } while (0) 00134 #define FMTV(def_pad, def_prec, fmt, val) \ 00135 do { \ 00136 VALUE tmp = (val); \ 00137 if (FIXNUM_P(tmp)) { \ 00138 FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ 00139 } \ 00140 else { \ 00141 VALUE args[2], result; \ 00142 size_t l; \ 00143 if (precision <= 0) precision = (def_prec); \ 00144 if (flags & BIT_OF(LEFT)) precision = 1; \ 00145 args[0] = INT2FIX(precision); \ 00146 args[1] = (val); \ 00147 if (padding == '0' || (!padding && (def_pad) == '0')) \ 00148 result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ 00149 else \ 00150 result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ 00151 l = strlcpy(s, StringValueCStr(result), endp - s); \ 00152 if ((size_t)(endp - s) <= l) \ 00153 goto err; \ 00154 s += l; \ 00155 } \ 00156 } while (0) 00157 00158 if (*format != '%') { 00159 *s++ = *format; 00160 continue; 00161 } 00162 tp = tbuf; 00163 sp = format; 00164 precision = -1; 00165 flags = 0; 00166 padding = 0; 00167 colons = 0; 00168 again: 00169 switch (*++format) { 00170 case '\0': 00171 format--; 00172 goto unknown; 00173 00174 case 'A': /* full weekday name */ 00175 case 'a': /* abbreviated weekday name */ 00176 if (flags & BIT_OF(CHCASE)) { 00177 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); 00178 flags |= BIT_OF(UPPER); 00179 } 00180 { 00181 int wday = tmx_wday; 00182 if (wday < 0 || wday > 6) 00183 i = 1, tp = "?"; 00184 else { 00185 if (*format == 'A') 00186 i = strlen(tp = days_l[wday]); 00187 else 00188 i = 3, tp = days_l[wday]; 00189 } 00190 } 00191 break; 00192 00193 case 'B': /* full month name */ 00194 case 'b': /* abbreviated month name */ 00195 case 'h': /* same as %b */ 00196 if (flags & BIT_OF(CHCASE)) { 00197 flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); 00198 flags |= BIT_OF(UPPER); 00199 } 00200 { 00201 int mon = tmx_mon; 00202 if (mon < 1 || mon > 12) 00203 i = 1, tp = "?"; 00204 else { 00205 if (*format == 'B') 00206 i = strlen(tp = months_l[mon - 1]); 00207 else 00208 i = 3, tp = months_l[mon - 1]; 00209 } 00210 } 00211 break; 00212 00213 case 'C': /* century (year/100) */ 00214 FMTV('0', 2, "d", div(tmx_year, INT2FIX(100))); 00215 continue; 00216 00217 case 'c': /* appropriate date and time representation */ 00218 STRFTIME("%a %b %e %H:%M:%S %Y"); 00219 continue; 00220 00221 case 'D': 00222 STRFTIME("%m/%d/%y"); 00223 continue; 00224 00225 case 'd': /* day of the month, 01 - 31 */ 00226 case 'e': /* day of month, blank padded */ 00227 v = range(1, tmx_mday, 31); 00228 FMT((*format == 'd') ? '0' : ' ', 2, "d", v); 00229 continue; 00230 00231 case 'F': 00232 STRFTIME("%Y-%m-%d"); 00233 continue; 00234 00235 case 'G': /* year of ISO week with century */ 00236 case 'Y': /* year with century */ 00237 { 00238 VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year; 00239 if (FIXNUM_P(year)) { 00240 long y = FIX2LONG(year); 00241 FMT('0', 0 <= y ? 4 : 5, "ld", y); 00242 } 00243 else { 00244 FMTV('0', 4, "d", year); 00245 } 00246 } 00247 continue; 00248 00249 case 'g': /* year of ISO week without a century */ 00250 case 'y': /* year without a century */ 00251 v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100))); 00252 FMT('0', 2, "d", v); 00253 continue; 00254 00255 case 'H': /* hour, 24-hour clock, 00 - 23 */ 00256 case 'k': /* hour, 24-hour clock, blank pad */ 00257 v = range(0, tmx_hour, 23); 00258 FMT((*format == 'H') ? '0' : ' ', 2, "d", v); 00259 continue; 00260 00261 case 'I': /* hour, 12-hour clock, 01 - 12 */ 00262 case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ 00263 v = range(0, tmx_hour, 23); 00264 if (v == 0) 00265 v = 12; 00266 else if (v > 12) 00267 v -= 12; 00268 FMT((*format == 'I') ? '0' : ' ', 2, "d", v); 00269 continue; 00270 00271 case 'j': /* day of the year, 001 - 366 */ 00272 v = range(1, tmx_yday, 366); 00273 FMT('0', 3, "d", v); 00274 continue; 00275 00276 case 'L': /* millisecond */ 00277 case 'N': /* nanosecond */ 00278 if (*format == 'L') 00279 w = 3; 00280 else 00281 w = 9; 00282 if (precision <= 0) 00283 precision = w; 00284 NEEDS(precision); 00285 00286 { 00287 VALUE subsec = tmx_sec_fraction; 00288 int ww; 00289 long n; 00290 00291 ww = precision; 00292 while (9 <= ww) { 00293 subsec = mul(subsec, INT2FIX(1000000000)); 00294 ww -= 9; 00295 } 00296 n = 1; 00297 for (; 0 < ww; ww--) 00298 n *= 10; 00299 if (n != 1) 00300 subsec = mul(subsec, INT2FIX(n)); 00301 subsec = div(subsec, INT2FIX(1)); 00302 00303 if (FIXNUM_P(subsec)) { 00304 (void)snprintf(s, endp - s, "%0*ld", 00305 precision, FIX2LONG(subsec)); 00306 s += precision; 00307 } 00308 else { 00309 VALUE args[2], result; 00310 args[0] = INT2FIX(precision); 00311 args[1] = subsec; 00312 result = rb_str_format(2, args, rb_str_new2("%0*d")); 00313 (void)strlcpy(s, StringValueCStr(result), endp - s); 00314 s += precision; 00315 } 00316 } 00317 continue; 00318 00319 case 'M': /* minute, 00 - 59 */ 00320 v = range(0, tmx_min, 59); 00321 FMT('0', 2, "d", v); 00322 continue; 00323 00324 case 'm': /* month, 01 - 12 */ 00325 v = range(1, tmx_mon, 12); 00326 FMT('0', 2, "d", v); 00327 continue; 00328 00329 case 'n': /* same as \n */ 00330 FILL_PADDING(1); 00331 *s++ = '\n'; 00332 continue; 00333 00334 case 't': /* same as \t */ 00335 FILL_PADDING(1); 00336 *s++ = '\t'; 00337 continue; 00338 00339 case 'P': /* am or pm based on 12-hour clock */ 00340 case 'p': /* AM or PM based on 12-hour clock */ 00341 if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || 00342 (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) { 00343 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); 00344 flags |= BIT_OF(LOWER); 00345 } 00346 v = range(0, tmx_hour, 23); 00347 if (v < 12) 00348 tp = ampm[0]; 00349 else 00350 tp = ampm[1]; 00351 i = 2; 00352 break; 00353 00354 case 'Q': /* milliseconds since Unix epoch */ 00355 FMTV('0', 1, "d", tmx_msecs); 00356 continue; 00357 00358 case 'R': 00359 STRFTIME("%H:%M"); 00360 continue; 00361 00362 case 'r': 00363 STRFTIME("%I:%M:%S %p"); 00364 continue; 00365 00366 case 'S': /* second, 00 - 59 */ 00367 v = range(0, tmx_sec, 59); 00368 FMT('0', 2, "d", v); 00369 continue; 00370 00371 case 's': /* seconds since Unix epoch */ 00372 FMTV('0', 1, "d", tmx_secs); 00373 continue; 00374 00375 case 'T': 00376 STRFTIME("%H:%M:%S"); 00377 continue; 00378 00379 case 'U': /* week of year, Sunday is first day of week */ 00380 case 'W': /* week of year, Monday is first day of week */ 00381 v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53); 00382 FMT('0', 2, "d", v); 00383 continue; 00384 00385 case 'u': /* weekday, Monday == 1, 1 - 7 */ 00386 v = range(1, tmx_cwday, 7); 00387 FMT('0', 1, "d", v); 00388 continue; 00389 00390 case 'V': /* week of year according ISO 8601 */ 00391 v = range(1, tmx_cweek, 53); 00392 FMT('0', 2, "d", v); 00393 continue; 00394 00395 case 'v': 00396 STRFTIME("%e-%b-%Y"); 00397 continue; 00398 00399 case 'w': /* weekday, Sunday == 0, 0 - 6 */ 00400 v = range(0, tmx_wday, 6); 00401 FMT('0', 1, "d", v); 00402 continue; 00403 00404 case 'X': /* appropriate time representation */ 00405 STRFTIME("%H:%M:%S"); 00406 continue; 00407 00408 case 'x': /* appropriate date representation */ 00409 STRFTIME("%m/%d/%y"); 00410 continue; 00411 00412 case 'Z': /* time zone name or abbreviation */ 00413 if (flags & BIT_OF(CHCASE)) { 00414 flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); 00415 flags |= BIT_OF(LOWER); 00416 } 00417 { 00418 char *zone = tmx_zone; 00419 if (zone == NULL) 00420 tp = ""; 00421 else 00422 tp = zone; 00423 i = strlen(tp); 00424 } 00425 break; 00426 00427 case 'z': /* offset from UTC */ 00428 { 00429 long off, aoff; 00430 int hl, hw; 00431 00432 off = tmx_offset; 00433 aoff = off; 00434 if (aoff < 0) 00435 aoff = -off; 00436 00437 if ((aoff / 3600) < 10) 00438 hl = 1; 00439 else 00440 hl = 2; 00441 hw = 2; 00442 if (flags & BIT_OF(LEFT) && hl == 1) 00443 hw = 1; 00444 00445 switch (colons) { 00446 case 0: /* %z -> +hhmm */ 00447 precision = precision <= (3 + hw) ? hw : precision - 3; 00448 NEEDS(precision + 3); 00449 break; 00450 00451 case 1: /* %:z -> +hh:mm */ 00452 precision = precision <= (4 + hw) ? hw : precision - 4; 00453 NEEDS(precision + 4); 00454 break; 00455 00456 case 2: /* %::z -> +hh:mm:ss */ 00457 precision = precision <= (7 + hw) ? hw : precision - 7; 00458 NEEDS(precision + 7); 00459 break; 00460 00461 case 3: /* %:::z -> +hh[:mm[:ss]] */ 00462 { 00463 if (aoff % 3600 == 0) { 00464 precision = precision <= (1 + hw) ? 00465 hw : precision - 1; 00466 NEEDS(precision + 3); 00467 } 00468 else if (aoff % 60 == 0) { 00469 precision = precision <= (4 + hw) ? 00470 hw : precision - 4; 00471 NEEDS(precision + 4); 00472 } 00473 else { 00474 precision = precision <= (7 + hw) ? 00475 hw : precision - 7; 00476 NEEDS(precision + 7); 00477 } 00478 } 00479 break; 00480 00481 default: 00482 format--; 00483 goto unknown; 00484 } 00485 if (padding == ' ' && precision > hl) { 00486 i = snprintf(s, endp - s, "%*s", precision - hl, ""); 00487 precision = hl; 00488 if (i < 0) goto err; 00489 s += i; 00490 } 00491 if (off < 0) { 00492 off = -off; 00493 *s++ = '-'; 00494 } else { 00495 *s++ = '+'; 00496 } 00497 i = snprintf(s, endp - s, "%.*ld", precision, off / 3600); 00498 if (i < 0) goto err; 00499 s += i; 00500 off = off % 3600; 00501 if (colons == 3 && off == 0) 00502 continue; 00503 if (1 <= colons) 00504 *s++ = ':'; 00505 i = snprintf(s, endp - s, "%02d", (int)(off / 60)); 00506 if (i < 0) goto err; 00507 s += i; 00508 off = off % 60; 00509 if (colons == 3 && off == 0) 00510 continue; 00511 if (2 <= colons) { 00512 *s++ = ':'; 00513 i = snprintf(s, endp - s, "%02d", (int)off); 00514 if (i < 0) goto err; 00515 s += i; 00516 } 00517 } 00518 continue; 00519 00520 case '+': 00521 STRFTIME("%a %b %e %H:%M:%S %Z %Y"); 00522 continue; 00523 00524 case 'E': 00525 /* POSIX locale extensions, ignored for now */ 00526 flags |= BIT_OF(LOCALE_E); 00527 if (*(format + 1) && strchr("cCxXyY", *(format + 1))) 00528 goto again; 00529 goto unknown; 00530 case 'O': 00531 /* POSIX locale extensions, ignored for now */ 00532 flags |= BIT_OF(LOCALE_O); 00533 if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1))) 00534 goto again; 00535 goto unknown; 00536 00537 case ':': 00538 flags |= BIT_OF(COLONS); 00539 { 00540 size_t l = strspn(format, ":"); 00541 format += l; 00542 if (*format == 'z') { 00543 colons = l; 00544 format--; 00545 goto again; 00546 } 00547 format -= l; 00548 } 00549 goto unknown; 00550 00551 case '_': 00552 FLAG_FOUND(); 00553 padding = ' '; 00554 goto again; 00555 00556 case '-': 00557 FLAG_FOUND(); 00558 flags |= BIT_OF(LEFT); 00559 goto again; 00560 00561 case '^': 00562 FLAG_FOUND(); 00563 flags |= BIT_OF(UPPER); 00564 goto again; 00565 00566 case '#': 00567 FLAG_FOUND(); 00568 flags |= BIT_OF(CHCASE); 00569 goto again; 00570 00571 case '0': 00572 FLAG_FOUND(); 00573 padding = '0'; 00574 case '1': case '2': case '3': case '4': 00575 case '5': case '6': case '7': case '8': case '9': 00576 { 00577 char *e; 00578 precision = (int)strtoul(format, &e, 10); 00579 format = e - 1; 00580 goto again; 00581 } 00582 00583 case '%': 00584 FILL_PADDING(1); 00585 *s++ = '%'; 00586 continue; 00587 00588 default: 00589 unknown: 00590 i = format - sp + 1; 00591 tp = sp; 00592 precision = -1; 00593 flags = 0; 00594 padding = 0; 00595 colons = 0; 00596 break; 00597 } 00598 if (i) { 00599 FILL_PADDING(i); 00600 memcpy(s, tp, i); 00601 switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) { 00602 case BIT_OF(UPPER): 00603 upcase(s, i); 00604 break; 00605 case BIT_OF(LOWER): 00606 downcase(s, i); 00607 break; 00608 } 00609 s += i; 00610 } 00611 } 00612 if (s >= endp) { 00613 goto err; 00614 } 00615 if (*format == '\0') { 00616 *s = '\0'; 00617 return (s - start); 00618 } 00619 return 0; 00620 } 00621 00622 size_t 00623 date_strftime(char *s, size_t maxsize, const char *format, 00624 const struct tmx *tmx) 00625 { 00626 return date_strftime_with_tmx(s, maxsize, format, tmx); 00627 } 00628 00629 /* 00630 Local variables: 00631 c-file-style: "ruby" 00632 End: 00633 */ 00634