Ruby  1.9.3p448(2013-06-27revision41675)
date_strptime.c
Go to the documentation of this file.
1 /*
2  date_strptime.c: Coded by Tadayoshi Funaba 2011,2012
3 */
4 
5 #include "ruby.h"
6 #include "ruby/encoding.h"
7 #include "ruby/re.h"
8 #include <ctype.h>
9 
10 static const char *day_names[] = {
11  "Sunday", "Monday", "Tuesday", "Wednesday",
12  "Thursday", "Friday", "Saturday",
13  "Sun", "Mon", "Tue", "Wed",
14  "Thu", "Fri", "Sat"
15 };
16 
17 static const char *month_names[] = {
18  "January", "February", "March", "April",
19  "May", "June", "July", "August", "September",
20  "October", "November", "December",
21  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
22  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
23 };
24 
25 static const char *merid_names[] = {
26  "am", "pm",
27  "a.m.", "p.m."
28 };
29 
30 static const char *extz_pats[] = {
31  ":z",
32  "::z",
33  ":::z"
34 };
35 
36 #define sizeof_array(o) (sizeof o / sizeof o[0])
37 
38 #define f_negate(x) rb_funcall(x, rb_intern("-@"), 0)
39 #define f_add(x,y) rb_funcall(x, '+', 1, y)
40 #define f_sub(x,y) rb_funcall(x, '-', 1, y)
41 #define f_mul(x,y) rb_funcall(x, '*', 1, y)
42 #define f_div(x,y) rb_funcall(x, '/', 1, y)
43 #define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y)
44 #define f_mod(x,y) rb_funcall(x, '%', 1, y)
45 #define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y)
46 
47 #define f_lt_p(x,y) rb_funcall(x, '<', 1, y)
48 #define f_gt_p(x,y) rb_funcall(x, '>', 1, y)
49 #define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y)
50 #define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y)
51 
52 #define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s)
53 #define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i)
54 #define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i)
55 
56 #define issign(c) ((c) == '-' || (c) == '+')
57 
58 static int
59 num_pattern_p(const char *s)
60 {
61  if (isdigit(*s))
62  return 1;
63  if (*s == '%') {
64  s++;
65  if (*s == 'E' || *s == 'O')
66  s++;
67  if (*s &&
68  (strchr("CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy", *s) || isdigit(*s)))
69  return 1;
70  }
71  return 0;
72 }
73 
74 #define NUM_PATTERN_P() num_pattern_p(&fmt[fi + 1])
75 
76 static long
77 read_digits(const char *s, VALUE *n, size_t width)
78 {
79  size_t l;
80 
81  l = strspn(s, "0123456789");
82 
83  if (l == 0)
84  return 0;
85 
86  if (width < l)
87  l = width;
88 
89  if ((4 * l * sizeof(char)) <= (sizeof(long)*CHAR_BIT)) {
90  const char *os = s;
91  long v;
92 
93  v = 0;
94  while ((size_t)(s - os) < l) {
95  v *= 10;
96  v += *s - '0';
97  s++;
98  }
99  if (os == s)
100  return 0;
101  *n = LONG2NUM(v);
102  return l;
103  }
104  else {
105  char *s2 = ALLOCA_N(char, l + 1);
106  memcpy(s2, s, l);
107  s2[l] = '\0';
108  *n = rb_cstr_to_inum(s2, 10, 0);
109  return l;
110  }
111 }
112 
113 #define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k)), v)
114 #define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k)))
115 #define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k)))
116 
117 #define fail() \
118 { \
119  set_hash("_fail", Qtrue); \
120  return 0; \
121 }
122 
123 #define fail_p() (!NIL_P(ref_hash("_fail")))
124 
125 #define READ_DIGITS(n,w) \
126 { \
127  size_t l; \
128  l = read_digits(&str[si], &n, w); \
129  if (l == 0) \
130  fail(); \
131  si += l; \
132 }
133 
134 #define READ_DIGITS_MAX(n) READ_DIGITS(n, LONG_MAX)
135 
136 static int
137 valid_range_p(VALUE v, int a, int b)
138 {
139  if (FIXNUM_P(v)) {
140  int vi = FIX2INT(v);
141  return !(vi < a || vi > b);
142  }
143  return !(f_lt_p(v, INT2NUM(a)) || f_gt_p(v, INT2NUM(b)));
144 }
145 
146 #define recur(fmt) \
147 { \
148  size_t l; \
149  l = date__strptime_internal(&str[si], slen - si, \
150  fmt, sizeof fmt - 1, hash); \
151  if (fail_p()) \
152  return 0; \
153  si += l; \
154 }
155 
157 
158 static size_t
159 date__strptime_internal(const char *str, size_t slen,
160  const char *fmt, size_t flen, VALUE hash)
161 {
162  size_t si, fi;
163  int c;
164 
165  si = fi = 0;
166 
167  while (fi < flen) {
168 
169  switch (fmt[fi]) {
170  case '%':
171 
172  again:
173  fi++;
174  c = fmt[fi];
175 
176  switch (c) {
177  case 'E':
178  if (fmt[fi + 1] && strchr("cCxXyY", fmt[fi + 1]))
179  goto again;
180  fi--;
181  goto ordinal;
182  case 'O':
183  if (fmt[fi + 1] && strchr("deHImMSuUVwWy", fmt[fi + 1]))
184  goto again;
185  fi--;
186  goto ordinal;
187  case ':':
188  {
189  int i;
190 
191  for (i = 0; i < (int)sizeof_array(extz_pats); i++)
192  if (strncmp(extz_pats[i], &fmt[fi],
193  strlen(extz_pats[i])) == 0) {
194  fi += i;
195  goto again;
196  }
197  fail();
198  }
199 
200  case 'A':
201  case 'a':
202  {
203  int i;
204 
205  for (i = 0; i < (int)sizeof_array(day_names); i++) {
206  size_t l = strlen(day_names[i]);
207  if (strncasecmp(day_names[i], &str[si], l) == 0) {
208  si += l;
209  set_hash("wday", INT2FIX(i % 7));
210  goto matched;
211  }
212  }
213  fail();
214  }
215  case 'B':
216  case 'b':
217  case 'h':
218  {
219  int i;
220 
221  for (i = 0; i < (int)sizeof_array(month_names); i++) {
222  size_t l = strlen(month_names[i]);
223  if (strncasecmp(month_names[i], &str[si], l) == 0) {
224  si += l;
225  set_hash("mon", INT2FIX((i % 12) + 1));
226  goto matched;
227  }
228  }
229  fail();
230  }
231 
232  case 'C':
233  {
234  VALUE n;
235 
236  if (NUM_PATTERN_P())
237  READ_DIGITS(n, 2)
238  else
239  READ_DIGITS_MAX(n)
240  set_hash("_cent", n);
241  goto matched;
242  }
243 
244  case 'c':
245  recur("%a %b %e %H:%M:%S %Y");
246  goto matched;
247 
248  case 'D':
249  recur("%m/%d/%y");
250  goto matched;
251 
252  case 'd':
253  case 'e':
254  {
255  VALUE n;
256 
257  if (str[si] == ' ') {
258  si++;
259  READ_DIGITS(n, 1);
260  } else {
261  READ_DIGITS(n, 2);
262  }
263  if (!valid_range_p(n, 1, 31))
264  fail();
265  set_hash("mday", n);
266  goto matched;
267  }
268 
269  case 'F':
270  recur("%Y-%m-%d");
271  goto matched;
272 
273  case 'G':
274  {
275  VALUE n;
276 
277  if (NUM_PATTERN_P())
278  READ_DIGITS(n, 4)
279  else
280  READ_DIGITS_MAX(n)
281  set_hash("cwyear", n);
282  goto matched;
283  }
284 
285  case 'g':
286  {
287  VALUE n;
288 
289  READ_DIGITS(n, 2);
290  if (!valid_range_p(n, 0, 99))
291  fail();
292  set_hash("cwyear",n);
293  set_hash("_cent",
294  INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20));
295  goto matched;
296  }
297 
298  case 'H':
299  case 'k':
300  {
301  VALUE n;
302 
303  if (str[si] == ' ') {
304  si++;
305  READ_DIGITS(n, 1);
306  } else {
307  READ_DIGITS(n, 2);
308  }
309  if (!valid_range_p(n, 0, 24))
310  fail();
311  set_hash("hour", n);
312  goto matched;
313  }
314 
315  case 'I':
316  case 'l':
317  {
318  VALUE n;
319 
320  if (str[si] == ' ') {
321  si++;
322  READ_DIGITS(n, 1);
323  } else {
324  READ_DIGITS(n, 2);
325  }
326  if (!valid_range_p(n, 1, 12))
327  fail();
328  set_hash("hour", n);
329  goto matched;
330  }
331 
332  case 'j':
333  {
334  VALUE n;
335 
336  READ_DIGITS(n, 3);
337  if (!valid_range_p(n, 1, 366))
338  fail();
339  set_hash("yday", n);
340  goto matched;
341  }
342 
343  case 'L':
344  case 'N':
345  {
346  VALUE n;
347  int sign = 1;
348  size_t osi;
349 
350  if (issign(str[si])) {
351  if (str[si] == '-')
352  sign = -1;
353  si++;
354  }
355  osi = si;
356  if (NUM_PATTERN_P())
357  READ_DIGITS(n, c == 'L' ? 3 : 9)
358  else
359  READ_DIGITS_MAX(n)
360  if (sign == -1)
361  n = f_negate(n);
362  set_hash("sec_fraction",
364  f_expt(INT2FIX(10),
365  ULONG2NUM(si - osi))));
366  goto matched;
367  }
368 
369  case 'M':
370  {
371  VALUE n;
372 
373  READ_DIGITS(n, 2);
374  if (!valid_range_p(n, 0, 59))
375  fail();
376  set_hash("min", n);
377  goto matched;
378  }
379 
380  case 'm':
381  {
382  VALUE n;
383 
384  READ_DIGITS(n, 2);
385  if (!valid_range_p(n, 1, 12))
386  fail();
387  set_hash("mon", n);
388  goto matched;
389  }
390 
391  case 'n':
392  case 't':
393  recur(" ");
394  goto matched;
395 
396  case 'P':
397  case 'p':
398  {
399  int i;
400 
401  for (i = 0; i < 4; i++) {
402  size_t l = strlen(merid_names[i]);
403  if (strncasecmp(merid_names[i], &str[si], l) == 0) {
404  si += l;
405  set_hash("_merid", INT2FIX((i % 2) == 0 ? 0 : 12));
406  goto matched;
407  }
408  }
409  fail();
410  }
411 
412  case 'Q':
413  {
414  VALUE n;
415  int sign = 1;
416 
417  if (str[si] == '-') {
418  sign = -1;
419  si++;
420  }
421  READ_DIGITS_MAX(n);
422  if (sign == -1)
423  n = f_negate(n);
424  set_hash("seconds",
426  f_expt(INT2FIX(10),
427  INT2FIX(3))));
428  goto matched;
429  }
430 
431  case 'R':
432  recur("%H:%M");
433  goto matched;
434 
435  case 'r':
436  recur("%I:%M:%S %p");
437  goto matched;
438 
439  case 'S':
440  {
441  VALUE n;
442 
443  READ_DIGITS(n, 2);
444  if (!valid_range_p(n, 0, 60))
445  fail();
446  set_hash("sec", n);
447  goto matched;
448  }
449 
450  case 's':
451  {
452  VALUE n;
453  int sign = 1;
454 
455  if (str[si] == '-') {
456  sign = -1;
457  si++;
458  }
459  READ_DIGITS_MAX(n);
460  if (sign == -1)
461  n = f_negate(n);
462  set_hash("seconds", n);
463  goto matched;
464  }
465 
466  case 'T':
467  recur("%H:%M:%S");
468  goto matched;
469 
470  case 'U':
471  case 'W':
472  {
473  VALUE n;
474 
475  READ_DIGITS(n, 2);
476  if (!valid_range_p(n, 0, 53))
477  fail();
478  set_hash(c == 'U' ? "wnum0" : "wnum1", n);
479  goto matched;
480  }
481 
482  case 'u':
483  {
484  VALUE n;
485 
486  READ_DIGITS(n, 1);
487  if (!valid_range_p(n, 1, 7))
488  fail();
489  set_hash("cwday", n);
490  goto matched;
491  }
492 
493  case 'V':
494  {
495  VALUE n;
496 
497  READ_DIGITS(n, 2);
498  if (!valid_range_p(n, 1, 53))
499  fail();
500  set_hash("cweek", n);
501  goto matched;
502  }
503 
504  case 'v':
505  recur("%e-%b-%Y");
506  goto matched;
507 
508  case 'w':
509  {
510  VALUE n;
511 
512  READ_DIGITS(n, 1);
513  if (!valid_range_p(n, 0, 6))
514  fail();
515  set_hash("wday", n);
516  goto matched;
517  }
518 
519  case 'X':
520  recur("%H:%M:%S");
521  goto matched;
522 
523  case 'x':
524  recur("%m/%d/%y");
525  goto matched;
526 
527  case 'Y':
528  {
529  VALUE n;
530  int sign = 1;
531 
532  if (issign(str[si])) {
533  if (str[si] == '-')
534  sign = -1;
535  si++;
536  }
537  if (NUM_PATTERN_P())
538  READ_DIGITS(n, 4)
539  else
540  READ_DIGITS_MAX(n)
541  if (sign == -1)
542  n = f_negate(n);
543  set_hash("year", n);
544  goto matched;
545  }
546 
547  case 'y':
548  {
549  VALUE n;
550  int sign = 1;
551 
552  READ_DIGITS(n, 2);
553  if (!valid_range_p(n, 0, 99))
554  fail();
555  if (sign == -1)
556  n = f_negate(n);
557  set_hash("year", n);
558  set_hash("_cent",
559  INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20));
560  goto matched;
561  }
562 
563  case 'Z':
564  case 'z':
565  {
566  static const char pat_source[] =
567  "\\A("
568  "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?"
569  "|[[:alpha:].\\s]+(?:standard|daylight)\\s+time\\b"
570  "|[[:alpha:]]+(?:\\s+dst)?\\b"
571  ")";
572  static VALUE pat = Qnil;
573  VALUE m, b;
574 
575  if (NIL_P(pat)) {
576  pat = rb_reg_new(pat_source, sizeof pat_source - 1,
579  }
580 
581  b = rb_backref_get();
582  rb_match_busy(b);
583  m = f_match(pat, rb_usascii_str_new2(&str[si]));
584 
585  if (!NIL_P(m)) {
586  VALUE s, l, o;
587 
588  s = rb_reg_nth_match(1, m);
589  l = f_end(m, INT2FIX(0));
590  o = date_zone_to_diff(s);
591  si += NUM2LONG(l);
592  set_hash("zone", s);
593  set_hash("offset", o);
594  rb_backref_set(b);
595  goto matched;
596  }
597  rb_backref_set(b);
598  fail();
599  }
600 
601  case '%':
602  if (str[si] != '%')
603  fail();
604  si++;
605  goto matched;
606 
607  case '+':
608  recur("%a %b %e %H:%M:%S %Z %Y");
609  goto matched;
610 
611  default:
612  if (str[si] != '%')
613  fail();
614  si++;
615  if (fi < flen)
616  if (str[si] != fmt[fi])
617  fail();
618  si++;
619  goto matched;
620  }
621  case ' ':
622  case '\t':
623  case '\n':
624  case '\v':
625  case '\f':
626  case '\r':
627  while (isspace(str[si]))
628  si++;
629  fi++;
630  break;
631  default:
632  ordinal:
633  if (str[si] != fmt[fi])
634  fail();
635  si++;
636  fi++;
637  break;
638  matched:
639  fi++;
640  break;
641  }
642  }
643 
644  return si;
645 }
646 
647 VALUE
648 date__strptime(const char *str, size_t slen,
649  const char *fmt, size_t flen, VALUE hash)
650 {
651  size_t si;
652  VALUE cent, merid;
653 
654  si = date__strptime_internal(str, slen, fmt, flen, hash);
655 
656  if (slen > si) {
657  VALUE s;
658 
659  s = rb_usascii_str_new(&str[si], slen - si);
660  set_hash("leftover", s);
661  }
662 
663  if (fail_p())
664  return Qnil;
665 
666  cent = ref_hash("_cent");
667  if (!NIL_P(cent)) {
668  VALUE year;
669 
670  year = ref_hash("cwyear");
671  if (!NIL_P(year))
672  set_hash("cwyear", f_add(year, f_mul(cent, INT2FIX(100))));
673  year = ref_hash("year");
674  if (!NIL_P(year))
675  set_hash("year", f_add(year, f_mul(cent, INT2FIX(100))));
676  del_hash("_cent");
677  }
678 
679  merid = ref_hash("_merid");
680  if (!NIL_P(merid)) {
681  VALUE hour;
682 
683  hour = ref_hash("hour");
684  if (!NIL_P(hour)) {
685  hour = f_mod(hour, INT2FIX(12));
686  set_hash("hour", f_add(hour, merid));
687  }
688  del_hash("_merid");
689  }
690 
691  return hash;
692 }
693 
694 /*
695 Local variables:
696 c-file-style: "ruby"
697 End:
698 */
699