diff options
Diffstat (limited to 'ext/date/date_parse.c')
-rw-r--r-- | ext/date/date_parse.c | 72 |
1 files changed, 53 insertions, 19 deletions
diff --git a/ext/date/date_parse.c b/ext/date/date_parse.c index 95274d5baa..c6f26ecb91 100644 --- a/ext/date/date_parse.c +++ b/ext/date/date_parse.c @@ -413,7 +413,6 @@ VALUE date_zone_to_diff(VALUE str) { VALUE offset = Qnil; - VALUE vbuf = 0; long l = RSTRING_LEN(str); const char *s = RSTRING_PTR(str); @@ -439,16 +438,26 @@ date_zone_to_diff(VALUE str) l -= w; dst = 1; } + { + const char *zn = s; long sl = shrunk_size(s, l); + char shrunk_buff[MAX_WORD_LENGTH]; /* no terminator to be added */ + const struct zone *z = 0; + + if (sl <= 0) { + sl = l; + } + else if (sl <= MAX_WORD_LENGTH) { + char *d = shrunk_buff; + sl = shrink_space(d, s, l); + zn = d; + } + if (sl > 0 && sl <= MAX_WORD_LENGTH) { - char *d = ALLOCV_N(char, vbuf, sl); - l = shrink_space(d, s, l); - s = d; + z = zonetab(zn, (unsigned int)sl); } - } - if (l > 0 && l <= MAX_WORD_LENGTH) { - const struct zone *z = zonetab(s, (unsigned int)l); + if (z) { int d = z->offset; if (dst) @@ -457,6 +466,7 @@ date_zone_to_diff(VALUE str) goto ok; } } + { char *p; int sign = 0; @@ -473,27 +483,53 @@ date_zone_to_diff(VALUE str) s++; l--; +#define out_of_range(v, min, max) ((v) < (min) || (max) < (v)) hour = STRTOUL(s, &p, 10); if (*p == ':') { + if (out_of_range(hour, 0, 23)) return Qnil; s = ++p; min = STRTOUL(s, &p, 10); + if (out_of_range(min, 0, 59)) return Qnil; if (*p == ':') { s = ++p; sec = STRTOUL(s, &p, 10); + if (out_of_range(sec, 0, 59)) return Qnil; } - goto num; } - if (*p == ',' || *p == '.') { - char *e = 0; - p++; - min = STRTOUL(p, &e, 10) * 3600; + else if (*p == ',' || *p == '.') { + /* fractional hour */ + size_t n; + int ov; + /* no over precision for offset; 10**-7 hour = 0.36 + * milliseconds should be enough. */ + const size_t max_digits = 7; /* 36 * 10**7 < 32-bit FIXNUM_MAX */ + + if (out_of_range(hour, 0, 23)) return Qnil; + + n = (s + l) - ++p; + if (n > max_digits) n = max_digits; + sec = ruby_scan_digits(p, n, 10, &n, &ov); + if ((p += n) < s + l && *p >= ('5' + !(sec & 1)) && *p <= '9') { + /* round half to even */ + sec++; + } + sec *= 36; if (sign) { hour = -hour; - min = -min; + sec = -sec; + } + if (n <= 2) { + /* HH.nn or HH.n */ + if (n == 1) sec *= 10; + offset = INT2FIX(sec + hour * 3600); + } + else { + VALUE denom = rb_int_positive_pow(10, (int)(n - 2)); + offset = f_add(rb_rational_new(INT2FIX(sec), denom), INT2FIX(hour * 3600)); + if (rb_rational_den(offset) == INT2FIX(1)) { + offset = rb_rational_num(offset); + } } - offset = rb_rational_new(INT2FIX(min), - rb_int_positive_pow(10, (int)(e - p))); - offset = f_add(INT2FIX(hour * 3600), offset); goto ok; } else if (l > 2) { @@ -506,18 +542,16 @@ date_zone_to_diff(VALUE str) min = ruby_scan_digits(&s[2 - l % 2], 2, 10, &n, &ov); if (l >= 5) sec = ruby_scan_digits(&s[4 - l % 2], 2, 10, &n, &ov); - goto num; } - num: sec += min * 60 + hour * 3600; if (sign) sec = -sec; offset = INT2FIX(sec); +#undef out_of_range } } } RB_GC_GUARD(str); ok: - ALLOCV_END(vbuf); return offset; } |