summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--benchmark/time_parse.yml2
-rw-r--r--test/ruby/test_time.rb21
-rw-r--r--time.c111
-rw-r--r--timev.rb15
4 files changed, 146 insertions, 3 deletions
diff --git a/benchmark/time_parse.yml b/benchmark/time_parse.yml
index a6d6948b9c..6060b58bc6 100644
--- a/benchmark/time_parse.yml
+++ b/benchmark/time_parse.yml
@@ -6,3 +6,5 @@ benchmark:
- Time.iso8601(iso8601)
- Time.parse(iso8601)
- Time.parse(inspect)
+ - Time.new(iso8601) rescue Time.iso8601(iso8601)
+ - Time.new(inspect) rescue Time.parse(inspect)
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb
index da87de62a5..ceeb3fbe55 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -57,6 +57,27 @@ class TestTime < Test::Unit::TestCase
assert_equal([0, 0, 0, 2, 1, 2000], Time.new(2000, 1, 1, 24, 0, 0, "-00:00").to_a[0, 6])
end
+ def test_new_from_string
+ assert_raise(ArgumentError) { Time.new(2021, 1, 1, "+09:99") }
+
+ t = Time.utc(2020, 12, 24, 15, 56, 17)
+ assert_equal(t, Time.new("2020-12-24T15:56:17Z"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +09:00"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +09:01:30"))
+ assert_equal(t, Time.new("2020-12-25 00:56:17 +0900"))
+ assert_equal(t, Time.new("2020-12-25 00:57:47 +090130"))
+ assert_equal(t, Time.new("2020-12-25T00:56:17+09:00"))
+ assert_equal(Time.utc(2020, 12, 24, 15, 56, 0), Time.new("2020-12-25 00:56 +09:00"))
+ assert_equal(Time.utc(2020, 12, 24, 15, 0, 0), Time.new("2020-12-25 00 +09:00"))
+
+ assert_equal(Time.new(2021, 12, 25, in: "+09:00"), Time.new("2021-12-25+09:00"))
+
+ assert_equal(0.123456r, Time.new("2021-12-25 00:00:00.123456 +09:00").subsec)
+ assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") {
+ Time.new("2020-12-25 00:56:17. +0900")
+ }
+ end
+
def test_time_add()
assert_equal(Time.utc(2000, 3, 21, 3, 30) + 3 * 3600,
Time.utc(2000, 3, 21, 6, 30))
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)
{
diff --git a/timev.rb b/timev.rb
index b549221315..1370a795a6 100644
--- a/timev.rb
+++ b/timev.rb
@@ -294,6 +294,13 @@ class Time
#
# Time.new # => 2021-04-24 17:27:46.0512465 -0500
#
+ # With one string argument that represents a time, returns a new
+ # \Time object based on the given argument, in the local timezone.
+ #
+ # Time.new('2000-12-31 23:59:59.5') # => 2000-12-31 23:59:59.5 -0600
+ # Time.new('2000-12-31 23:59:59.5 +0900') # => 2000-12-31 23:59:59.5 +0900
+ # Time.new('2000-12-31 23:59:59.5', in: '+0900') # => 2000-12-31 23:59:59.5 +0900
+ #
# With one to six arguments, returns a new \Time object
# based on the given arguments, in the local timezone.
#
@@ -368,7 +375,7 @@ class Time
# Time.new(in: '-12:00')
# # => 2022-08-23 08:49:26.1941467 -1200
#
- def initialize(year = (now = true), mon = nil, mday = nil, hour = nil, min = nil, sec = nil, zone = nil, in: nil)
+ def initialize(year = (now = true), mon = (str = year; nil), mday = nil, hour = nil, min = nil, sec = nil, zone = nil, in: nil)
if zone
if Primitive.arg!(:in)
raise ArgumentError, "timezone argument given as positional and keyword arguments"
@@ -381,6 +388,10 @@ class Time
return Primitive.time_init_now(zone)
end
- Primitive.time_init_args(year, mon, mday, hour, min, sec, zone)
+ if str and Primitive.time_init_parse(str, zone)
+ return self
+ end
+
+ Primitive.time_init_args(year, mon, mday, hour, min, sec, nil, zone)
end
end