ext/date/date_strptime.c

Go to the documentation of this file.
00001 /*
00002   date_strptime.c: Coded by Tadayoshi Funaba 2011,2012
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 Local variables:
00692 c-file-style: "ruby"
00693 End:
00694 */
00695 
00696 

Generated on 19 Jul 2016 for Ruby by  doxygen 1.4.7