aboutsummaryrefslogtreecommitdiffstats
path: root/time.c
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2021-09-05 00:24:23 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2022-12-16 22:52:59 +0900
commit8c272f44816f098c1e057c72a47451efc8cd1739 (patch)
treeb9c180b64a41b37e761c84001275816f283e3dfb /time.c
parentee7a338d2b21a84d194015c3680bd2a95c0dd23f (diff)
downloadruby-8c272f44816f098c1e057c72a47451efc8cd1739.tar.gz
[Feature #18033] Make Time.new parse time strings
`Time.new` now parses strings such as the result of `Time#inspect` and restricted ISO-8601 formats.
Diffstat (limited to 'time.c')
-rw-r--r--time.c111
1 files changed, 110 insertions, 1 deletions
diff --git a/time.c b/time.c
index a6bc03c9ae..560414e1f7 100644
--- a/time.c
+++ b/time.c
@@ -2350,7 +2350,8 @@ vtm_day_wraparound(struct vtm *vtm)
}
static VALUE
-time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VALUE mday, VALUE hour, VALUE min, VALUE sec, VALUE zone)
+time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VALUE mday,
+ VALUE hour, VALUE min, VALUE sec, VALUE subsec, VALUE zone)
{
struct vtm vtm;
VALUE utc = Qnil;
@@ -2374,6 +2375,10 @@ time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VA
vtm.sec = 0;
vtm.subsecx = INT2FIX(0);
}
+ else if (!NIL_P(subsec)) {
+ vtm.sec = obj2ubits(sec, 6);
+ vtm.subsecx = subsec;
+ }
else {
VALUE subsecx;
vtm.sec = obj2subsecx(sec, &subsecx);
@@ -2443,6 +2448,110 @@ time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VA
}
}
+static int
+two_digits(const char *ptr, const char *end, const char **endp)
+{
+ ssize_t len = end - ptr;
+ if (len < 2) return -1;
+ if (!ISDIGIT(ptr[0]) || !ISDIGIT(ptr[1])) return -1;
+ if ((len > 2) && ISDIGIT(ptr[2])) return -1;
+ *endp = ptr + 2;
+ return (ptr[0] - '0') * 10 + (ptr[1] - '0');
+}
+
+static VALUE
+parse_int(const char *ptr, const char *end, const char **endp, size_t *ndigits, bool sign)
+{
+ ssize_t len = (end - ptr);
+ int flags = sign ? RB_INT_PARSE_SIGN : 0;
+ return rb_int_parse_cstr(ptr, len, (char **)endp, ndigits, 10, flags);
+}
+
+static VALUE
+time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone)
+{
+ if (NIL_P(str = rb_check_string_type(str))) return Qnil;
+ if (!rb_enc_str_asciicompat_p(str)) {
+ rb_raise(rb_eArgError, "time string should have ASCII compatible encoding");
+ }
+
+ const char *const begin = RSTRING_PTR(str);
+ const char *const end = RSTRING_END(str);
+ const char *ptr = begin;
+ VALUE year = Qnil, subsec = Qnil;
+ int mon = -1, mday = -1, hour = -1, min = -1, sec = -1;
+ size_t ndigits;
+
+ while ((ptr < end) && ISSPACE(*ptr)) ptr++;
+ year = parse_int(ptr, end, &ptr, &ndigits, true);
+ if (NIL_P(year)) {
+ rb_raise(rb_eArgError, "can't parse: %+"PRIsVALUE, str);
+ }
+ do {
+#define peek_n(c, n) ((ptr + n < end) && ((unsigned char)ptr[n] == (c)))
+#define peek(c) peek_n(c, 0)
+#define peekc_n(n) ((ptr + n < end) ? (int)(unsigned char)ptr[n] : -1)
+#define peekc() peekc_n(0)
+ if (!peek('-')) break;
+ if ((mon = two_digits(ptr + 1, end, &ptr)) < 0) break;
+ if (!peek('-')) break;
+ if ((mday = two_digits(ptr + 1, end, &ptr)) < 0) break;
+ if (!peek(' ') && !peek('T')) break;
+ const char *const time_part = ptr + 1;
+ if ((hour = two_digits(ptr + 1, end, &ptr)) < 0) break;
+ if (!peek(':')) break;
+ if ((min = two_digits(ptr + 1, end, &ptr)) < 0) break;
+ if (!peek(':')) break;
+ if ((sec = two_digits(ptr + 1, end, &ptr)) < 0) break;
+ if (peek('.')) {
+ ptr++;
+ for (ndigits = 0; ndigits < 45 && ISDIGIT(peekc_n(ndigits)); ++ndigits);
+ if (!ndigits) {
+ int clen = rb_enc_precise_mbclen(ptr, end, rb_enc_get(str));
+ if (clen < 0) clen = 0;
+ rb_raise(rb_eArgError, "subsecond expected after dot: %.*s",
+ (int)(ptr - time_part) + clen, time_part);
+ }
+ subsec = parse_int(ptr, ptr + ndigits, &ptr, &ndigits, false);
+ if (NIL_P(subsec)) break;
+ while (ptr < end && ISDIGIT(*ptr)) ptr++;
+ }
+ } while (0);
+ while (ptr < end && ISSPACE(*ptr)) ptr++;
+ const char *const zstr = ptr;
+ while (ptr < end && !ISSPACE(*ptr)) ptr++;
+ const char *const zend = ptr;
+ while (ptr < end && ISSPACE(*ptr)) ptr++;
+ if (ptr < end) {
+ VALUE mesg = rb_str_new_cstr("can't parse at: ");
+ rb_str_cat(mesg, ptr, end - ptr);
+ rb_exc_raise(rb_exc_new_str(rb_eArgError, mesg));
+ }
+ if (zend > zstr) {
+ zone = rb_str_subseq(str, zstr - begin, zend - zstr);
+ }
+ if (!NIL_P(subsec)) {
+ /* subseconds is the last using ndigits */
+ if (ndigits < 9) {
+ VALUE mul = rb_int_positive_pow(10, 9 - ndigits);
+ subsec = rb_int_mul(subsec, mul);
+ }
+ else if (ndigits > 9) {
+ VALUE num = rb_int_positive_pow(10, ndigits - 9);
+ subsec = rb_rational_new(subsec, num);
+ }
+ }
+
+#define non_negative(x) ((x) < 0 ? Qnil : INT2FIX(x))
+ return time_init_args(ec, time, year,
+ non_negative(mon),
+ non_negative(mday),
+ non_negative(hour),
+ non_negative(min),
+ non_negative(sec),
+ subsec, zone);
+}
+
static void
subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec)
{