summaryrefslogtreecommitdiff
path: root/ext/date/date_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/date/date_parse.c')
-rw-r--r--ext/date/date_parse.c72
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;
}