00001
00002
00003
00004
00005 #include "ruby.h"
00006 #include "ruby/encoding.h"
00007 #include "ruby/re.h"
00008 #include <ctype.h>
00009
00010 static const char *day_names[] = {
00011 "Sunday", "Monday", "Tuesday", "Wednesday",
00012 "Thursday", "Friday", "Saturday",
00013 "Sun", "Mon", "Tue", "Wed",
00014 "Thu", "Fri", "Sat"
00015 };
00016
00017 static const char *month_names[] = {
00018 "January", "February", "March", "April",
00019 "May", "June", "July", "August", "September",
00020 "October", "November", "December",
00021 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00022 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00023 };
00024
00025 static const char *merid_names[] = {
00026 "am", "pm",
00027 "a.m.", "p.m."
00028 };
00029
00030 static const char *extz_pats[] = {
00031 ":z",
00032 "::z",
00033 ":::z"
00034 };
00035
00036 #define sizeof_array(o) (sizeof o / sizeof o[0])
00037
00038 #define f_negate(x) rb_funcall(x, rb_intern("-@"), 0)
#define f_add(x,y) rb_funcall(x, '+', 1, y)
#define f_sub(x,y) rb_funcall(x, '-', 1, y)
#define f_mul(x,y) rb_funcall(x, '*', 1, y)
#define f_div(x,y) rb_funcall(x, '/', 1, y)
#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y)
00039 #define f_mod(x,y) rb_funcall(x, '%', 1, y)
00040 #define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y)
00041
00042 #define f_lt_p(x,y) rb_funcall(x, '<', 1, y)
00043 #define f_gt_p(x,y) rb_funcall(x, '>', 1, y)
00044 #define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y)
00045 #define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y)
00046
00047 #define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s)
00048 #define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i)
00049 #define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i)
00050
00051 #define issign(c) ((c) == '-' || (c) == '+')
00052
00053 static int
00054 num_pattern_p(const char *s)
00055 {
00056 if (isdigit((unsigned char)*s))
00057 return 1;
00058 if (*s == '%') {
00059 s++;
00060 if (*s == 'E' || *s == 'O')
00061 s++;
00062 if (*s &&
00063 (strchr("CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy", *s) ||
00064 isdigit((unsigned char)*s)))
00065 return 1;
00066 }
00067 return 0;
00068 }
00069
00070 #define NUM_PATTERN_P() num_pattern_p(&fmt[fi + 1])
00071
00072 static long
00073 read_digits(const char *s, VALUE *n, size_t width)
00074 {
00075 size_t l;
00076
00077 l = strspn(s, "0123456789");
00078
00079 if (l == 0)
00080 return 0;
00081
00082 if (width < l)
00083 l = width;
00084
00085 if ((4 * l * sizeof(char)) <= (sizeof(long)*CHAR_BIT)) {
00086 const char *os = s;
00087 long v;
00088
00089 v = 0;
00090 while ((size_t)(s - os) < l) {
00091 v *= 10;
00092 v += *s - '0';
00093 s++;
00094 }
00095 if (os == s)
00096 return 0;
00097 *n = LONG2NUM(v);
00098 return l;
00099 }
00100 else {
00101 char *s2 = ALLOCA_N(char, l + 1);
00102 memcpy(s2, s, l);
00103 s2[l] = '\0';
00104 *n = rb_cstr_to_inum(s2, 10, 0);
00105 return l;
00106 }
00107 }
00108
00109 #define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k)), v)
00110 #define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k)))
00111 #define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k)))
00112
00113 #define fail() \
00114 { \
00115 set_hash("_fail", Qtrue); \
00116 return 0; \
00117 }
00118
00119 #define fail_p() (!NIL_P(ref_hash("_fail")))
00120
00121 #define READ_DIGITS(n,w) \
00122 { \
00123 size_t l; \
00124 l = read_digits(&str[si], &n, w); \
00125 if (l == 0) \
00126 fail(); \
00127 si += l; \
00128 }
00129
00130 #define READ_DIGITS_MAX(n) READ_DIGITS(n, LONG_MAX)
00131
00132 static int
00133 valid_range_p(VALUE v, int a, int b)
00134 {
00135 if (FIXNUM_P(v)) {
00136 int vi = FIX2INT(v);
00137 return !(vi < a || vi > b);
00138 }
00139 return !(f_lt_p(v, INT2NUM(a)) || f_gt_p(v, INT2NUM(b)));
00140 }
00141
00142 #define recur(fmt) \
00143 { \
00144 size_t l; \
00145 l = date__strptime_internal(&str[si], slen - si, \
00146 fmt, sizeof fmt - 1, hash); \
00147 if (fail_p()) \
00148 return 0; \
00149 si += l; \
00150 }
00151
00152 VALUE date_zone_to_diff(VALUE);
00153
00154 static size_t
00155 date__strptime_internal(const char *str, size_t slen,
00156 const char *fmt, size_t flen, VALUE hash)
00157 {
00158 size_t si, fi;
00159 int c;
00160
00161 si = fi = 0;
00162
00163 while (fi < flen) {
00164
00165 switch (fmt[fi]) {
00166 case '%':
00167
00168 again:
00169 fi++;
00170 c = fmt[fi];
00171
00172 switch (c) {
00173 case 'E':
00174 if (fmt[fi + 1] && strchr("cCxXyY", fmt[fi + 1]))
00175 goto again;
00176 fi--;
00177 goto ordinal;
00178 case 'O':
00179 if (fmt[fi + 1] && strchr("deHImMSuUVwWy", fmt[fi + 1]))
00180 goto again;
00181 fi--;
00182 goto ordinal;
00183 case ':':
00184 {
00185 int i;
00186
00187 for (i = 0; i < (int)sizeof_array(extz_pats); i++)
00188 if (strncmp(extz_pats[i], &fmt[fi],
00189 strlen(extz_pats[i])) == 0) {
00190 fi += i;
00191 goto again;
00192 }
00193 fail();
00194 }
00195
00196 case 'A':
00197 case 'a':
00198 {
00199 int i;
00200
00201 for (i = 0; i < (int)sizeof_array(day_names); i++) {
00202 size_t l = strlen(day_names[i]);
00203 if (strncasecmp(day_names[i], &str[si], l) == 0) {
00204 si += l;
00205 set_hash("wday", INT2FIX(i % 7));
00206 goto matched;
00207 }
00208 }
00209 fail();
00210 }
00211 case 'B':
00212 case 'b':
00213 case 'h':
00214 {
00215 int i;
00216
00217 for (i = 0; i < (int)sizeof_array(month_names); i++) {
00218 size_t l = strlen(month_names[i]);
00219 if (strncasecmp(month_names[i], &str[si], l) == 0) {
00220 si += l;
00221 set_hash("mon", INT2FIX((i % 12) + 1));
00222 goto matched;
00223 }
00224 }
00225 fail();
00226 }
00227
00228 case 'C':
00229 {
00230 VALUE n;
00231
00232 if (NUM_PATTERN_P())
00233 READ_DIGITS(n, 2)
00234 else
00235 READ_DIGITS_MAX(n)
00236 set_hash("_cent", n);
00237 goto matched;
00238 }
00239
00240 case 'c':
00241 recur("%a %b %e %H:%M:%S %Y");
00242 goto matched;
00243
00244 case 'D':
00245 recur("%m/%d/%y");
00246 goto matched;
00247
00248 case 'd':
00249 case 'e':
00250 {
00251 VALUE n;
00252
00253 if (str[si] == ' ') {
00254 si++;
00255 READ_DIGITS(n, 1);
00256 } else {
00257 READ_DIGITS(n, 2);
00258 }
00259 if (!valid_range_p(n, 1, 31))
00260 fail();
00261 set_hash("mday", n);
00262 goto matched;
00263 }
00264
00265 case 'F':
00266 recur("%Y-%m-%d");
00267 goto matched;
00268
00269 case 'G':
00270 {
00271 VALUE n;
00272
00273 if (NUM_PATTERN_P())
00274 READ_DIGITS(n, 4)
00275 else
00276 READ_DIGITS_MAX(n)
00277 set_hash("cwyear", n);
00278 goto matched;
00279 }
00280
00281 case 'g':
00282 {
00283 VALUE n;
00284
00285 READ_DIGITS(n, 2);
00286 if (!valid_range_p(n, 0, 99))
00287 fail();
00288 set_hash("cwyear",n);
00289 set_hash("_cent",
00290 INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20));
00291 goto matched;
00292 }
00293
00294 case 'H':
00295 case 'k':
00296 {
00297 VALUE n;
00298
00299 if (str[si] == ' ') {
00300 si++;
00301 READ_DIGITS(n, 1);
00302 } else {
00303 READ_DIGITS(n, 2);
00304 }
00305 if (!valid_range_p(n, 0, 24))
00306 fail();
00307 set_hash("hour", n);
00308 goto matched;
00309 }
00310
00311 case 'I':
00312 case 'l':
00313 {
00314 VALUE n;
00315
00316 if (str[si] == ' ') {
00317 si++;
00318 READ_DIGITS(n, 1);
00319 } else {
00320 READ_DIGITS(n, 2);
00321 }
00322 if (!valid_range_p(n, 1, 12))
00323 fail();
00324 set_hash("hour", n);
00325 goto matched;
00326 }
00327
00328 case 'j':
00329 {
00330 VALUE n;
00331
00332 READ_DIGITS(n, 3);
00333 if (!valid_range_p(n, 1, 366))
00334 fail();
00335 set_hash("yday", n);
00336 goto matched;
00337 }
00338
00339 case 'L':
00340 case 'N':
00341 {
00342 VALUE n;
00343 int sign = 1;
00344 size_t osi;
00345
00346 if (issign(str[si])) {
00347 if (str[si] == '-')
00348 sign = -1;
00349 si++;
00350 }
00351 osi = si;
00352 if (NUM_PATTERN_P())
00353 READ_DIGITS(n, c == 'L' ? 3 : 9)
00354 else
00355 READ_DIGITS_MAX(n)
00356 if (sign == -1)
00357 n = f_negate(n);
00358 set_hash("sec_fraction",
00359 rb_rational_new2(n,
00360 f_expt(INT2FIX(10),
00361 ULONG2NUM(si - osi))));
00362 goto matched;
00363 }
00364
00365 case 'M':
00366 {
00367 VALUE n;
00368
00369 READ_DIGITS(n, 2);
00370 if (!valid_range_p(n, 0, 59))
00371 fail();
00372 set_hash("min", n);
00373 goto matched;
00374 }
00375
00376 case 'm':
00377 {
00378 VALUE n;
00379
00380 READ_DIGITS(n, 2);
00381 if (!valid_range_p(n, 1, 12))
00382 fail();
00383 set_hash("mon", n);
00384 goto matched;
00385 }
00386
00387 case 'n':
00388 case 't':
00389 recur(" ");
00390 goto matched;
00391
00392 case 'P':
00393 case 'p':
00394 {
00395 int i;
00396
00397 for (i = 0; i < 4; i++) {
00398 size_t l = strlen(merid_names[i]);
00399 if (strncasecmp(merid_names[i], &str[si], l) == 0) {
00400 si += l;
00401 set_hash("_merid", INT2FIX((i % 2) == 0 ? 0 : 12));
00402 goto matched;
00403 }
00404 }
00405 fail();
00406 }
00407
00408 case 'Q':
00409 {
00410 VALUE n;
00411 int sign = 1;
00412
00413 if (str[si] == '-') {
00414 sign = -1;
00415 si++;
00416 }
00417 READ_DIGITS_MAX(n);
00418 if (sign == -1)
00419 n = f_negate(n);
00420 set_hash("seconds",
00421 rb_rational_new2(n,
00422 f_expt(INT2FIX(10),
00423 INT2FIX(3))));
00424 goto matched;
00425 }
00426
00427 case 'R':
00428 recur("%H:%M");
00429 goto matched;
00430
00431 case 'r':
00432 recur("%I:%M:%S %p");
00433 goto matched;
00434
00435 case 'S':
00436 {
00437 VALUE n;
00438
00439 READ_DIGITS(n, 2);
00440 if (!valid_range_p(n, 0, 60))
00441 fail();
00442 set_hash("sec", n);
00443 goto matched;
00444 }
00445
00446 case 's':
00447 {
00448 VALUE n;
00449 int sign = 1;
00450
00451 if (str[si] == '-') {
00452 sign = -1;
00453 si++;
00454 }
00455 READ_DIGITS_MAX(n);
00456 if (sign == -1)
00457 n = f_negate(n);
00458 set_hash("seconds", n);
00459 goto matched;
00460 }
00461
00462 case 'T':
00463 recur("%H:%M:%S");
00464 goto matched;
00465
00466 case 'U':
00467 case 'W':
00468 {
00469 VALUE n;
00470
00471 READ_DIGITS(n, 2);
00472 if (!valid_range_p(n, 0, 53))
00473 fail();
00474 set_hash(c == 'U' ? "wnum0" : "wnum1", n);
00475 goto matched;
00476 }
00477
00478 case 'u':
00479 {
00480 VALUE n;
00481
00482 READ_DIGITS(n, 1);
00483 if (!valid_range_p(n, 1, 7))
00484 fail();
00485 set_hash("cwday", n);
00486 goto matched;
00487 }
00488
00489 case 'V':
00490 {
00491 VALUE n;
00492
00493 READ_DIGITS(n, 2);
00494 if (!valid_range_p(n, 1, 53))
00495 fail();
00496 set_hash("cweek", n);
00497 goto matched;
00498 }
00499
00500 case 'v':
00501 recur("%e-%b-%Y");
00502 goto matched;
00503
00504 case 'w':
00505 {
00506 VALUE n;
00507
00508 READ_DIGITS(n, 1);
00509 if (!valid_range_p(n, 0, 6))
00510 fail();
00511 set_hash("wday", n);
00512 goto matched;
00513 }
00514
00515 case 'X':
00516 recur("%H:%M:%S");
00517 goto matched;
00518
00519 case 'x':
00520 recur("%m/%d/%y");
00521 goto matched;
00522
00523 case 'Y':
00524 {
00525 VALUE n;
00526 int sign = 1;
00527
00528 if (issign(str[si])) {
00529 if (str[si] == '-')
00530 sign = -1;
00531 si++;
00532 }
00533 if (NUM_PATTERN_P())
00534 READ_DIGITS(n, 4)
00535 else
00536 READ_DIGITS_MAX(n)
00537 if (sign == -1)
00538 n = f_negate(n);
00539 set_hash("year", n);
00540 goto matched;
00541 }
00542
00543 case 'y':
00544 {
00545 VALUE n;
00546 int sign = 1;
00547
00548 READ_DIGITS(n, 2);
00549 if (!valid_range_p(n, 0, 99))
00550 fail();
00551 if (sign == -1)
00552 n = f_negate(n);
00553 set_hash("year", n);
00554 set_hash("_cent",
00555 INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20));
00556 goto matched;
00557 }
00558
00559 case 'Z':
00560 case 'z':
00561 {
00562 static const char pat_source[] =
00563 "\\A("
00564 "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?"
00565 "|(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\s+time\\b"
00566 "|(?-i:[[:alpha:]]+)(?:\\s+dst)?\\b"
00567 ")";
00568 static VALUE pat = Qnil;
00569 VALUE m, b;
00570
00571 if (NIL_P(pat)) {
00572 pat = rb_reg_new(pat_source, sizeof pat_source - 1,
00573 ONIG_OPTION_IGNORECASE);
00574 rb_gc_register_mark_object(pat);
00575 }
00576
00577 b = rb_backref_get();
00578 rb_match_busy(b);
00579 m = f_match(pat, rb_usascii_str_new2(&str[si]));
00580
00581 if (!NIL_P(m)) {
00582 VALUE s, l, o;
00583
00584 s = rb_reg_nth_match(1, m);
00585 l = f_end(m, INT2FIX(0));
00586 o = date_zone_to_diff(s);
00587 si += NUM2LONG(l);
00588 set_hash("zone", s);
00589 set_hash("offset", o);
00590 rb_backref_set(b);
00591 goto matched;
00592 }
00593 rb_backref_set(b);
00594 fail();
00595 }
00596
00597 case '%':
00598 if (str[si] != '%')
00599 fail();
00600 si++;
00601 goto matched;
00602
00603 case '+':
00604 recur("%a %b %e %H:%M:%S %Z %Y");
00605 goto matched;
00606
00607 default:
00608 if (str[si] != '%')
00609 fail();
00610 si++;
00611 if (fi < flen)
00612 if (str[si] != fmt[fi])
00613 fail();
00614 si++;
00615 goto matched;
00616 }
00617 case ' ':
00618 case '\t':
00619 case '\n':
00620 case '\v':
00621 case '\f':
00622 case '\r':
00623 while (isspace((unsigned char)str[si]))
00624 si++;
00625 fi++;
00626 break;
00627 default:
00628 ordinal:
00629 if (str[si] != fmt[fi])
00630 fail();
00631 si++;
00632 fi++;
00633 break;
00634 matched:
00635 fi++;
00636 break;
00637 }
00638 }
00639
00640 return si;
00641 }
00642
00643 VALUE
00644 date__strptime(const char *str, size_t slen,
00645 const char *fmt, size_t flen, VALUE hash)
00646 {
00647 size_t si;
00648 VALUE cent, merid;
00649
00650 si = date__strptime_internal(str, slen, fmt, flen, hash);
00651
00652 if (slen > si) {
00653 VALUE s;
00654
00655 s = rb_usascii_str_new(&str[si], slen - si);
00656 set_hash("leftover", s);
00657 }
00658
00659 if (fail_p())
00660 return Qnil;
00661
00662 cent = ref_hash("_cent");
00663 if (!NIL_P(cent)) {
00664 VALUE year;
00665
00666 year = ref_hash("cwyear");
00667 if (!NIL_P(year))
00668 set_hash("cwyear", f_add(year, f_mul(cent, INT2FIX(100))));
00669 year = ref_hash("year");
00670 if (!NIL_P(year))
00671 set_hash("year", f_add(year, f_mul(cent, INT2FIX(100))));
00672 del_hash("_cent");
00673 }
00674
00675 merid = ref_hash("_merid");
00676 if (!NIL_P(merid)) {
00677 VALUE hour;
00678
00679 hour = ref_hash("hour");
00680 if (!NIL_P(hour)) {
00681 hour = f_mod(hour, INT2FIX(12));
00682 set_hash("hour", f_add(hour, merid));
00683 }
00684 del_hash("_merid");
00685 }
00686
00687 return hash;
00688 }
00689
00690
00691
00692
00693
00694
00695
00696