diff options
-rw-r--r-- | test/ruby/test_time.rb | 3 | ||||
-rw-r--r-- | time.c | 79 | ||||
-rw-r--r-- | timev.rb | 13 |
3 files changed, 68 insertions, 27 deletions
diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 9f79dfd1a7..02cf2e679d 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -73,6 +73,9 @@ class TestTime < Test::Unit::TestCase 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_equal(0.123456789r, Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec) + assert_equal(0.123r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3).subsec) + assert_equal(0.123456789876r, Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec) assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") { Time.new("2020-12-25 00:56:17. +0900") } @@ -515,17 +515,19 @@ wmod(wideval_t wx, wideval_t wy) } static VALUE -num_exact(VALUE v) +num_exact_check(VALUE v) { VALUE tmp; switch (TYPE(v)) { case T_FIXNUM: case T_BIGNUM: - return v; + tmp = v; + break; case T_RATIONAL: - return rb_rational_canonicalize(v); + tmp = rb_rational_canonicalize(v); + break; default: if (!UNDEF_P(tmp = rb_check_funcall(v, idTo_r, 0, NULL))) { @@ -535,10 +537,11 @@ num_exact(VALUE v) /* FALLTHROUGH */ } else if (RB_INTEGER_TYPE_P(tmp)) { - return tmp; + break; } else if (RB_TYPE_P(tmp, T_RATIONAL)) { - return rb_rational_canonicalize(tmp); + tmp = rb_rational_canonicalize(tmp); + break; } } else if (!NIL_P(tmp = rb_check_to_int(v))) { @@ -547,9 +550,26 @@ num_exact(VALUE v) case T_NIL: case T_STRING: - rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into an exact number", - rb_obj_class(v)); + return Qnil; } + ASSUME(!NIL_P(tmp)); + return tmp; +} + +NORETURN(static void num_exact_fail(VALUE v)); +static void +num_exact_fail(VALUE v) +{ + rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into an exact number", + rb_obj_class(v)); +} + +static VALUE +num_exact(VALUE v) +{ + VALUE num = num_exact_check(v); + if (NIL_P(num)) num_exact_fail(v); + return num; } /* time_t */ @@ -2349,13 +2369,13 @@ vtm_day_wraparound(struct vtm *vtm) vtm_add_day(vtm, 1); } +static VALUE time_init_vtm(VALUE time, struct vtm vtm, VALUE zone); + 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 subsec, VALUE zone) + VALUE hour, VALUE min, VALUE sec, VALUE zone) { struct vtm vtm; - VALUE utc = Qnil; - struct time_object *tobj; vtm.wday = VTM_WDAY_INITVAL; vtm.yday = 0; @@ -2375,16 +2395,21 @@ 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); vtm.subsecx = subsecx; } + return time_init_vtm(time, vtm, zone); +} + +static VALUE +time_init_vtm(VALUE time, struct vtm vtm, VALUE zone) +{ + VALUE utc = Qnil; + struct time_object *tobj; + vtm.isdst = VTM_ISDST_INITVAL; vtm.utc_offset = Qnil; const VALUE arg = zone; @@ -2474,7 +2499,7 @@ parse_int(const char *ptr, const char *end, const char **endp, size_t *ndigits, } static VALUE -time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone) +time_init_parse(rb_execution_context_t *ec, VALUE klass, VALUE str, VALUE zone, VALUE precision) { if (NIL_P(str = rb_check_string_type(str))) return Qnil; if (!rb_enc_str_asciicompat_p(str)) { @@ -2487,6 +2512,7 @@ time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone) VALUE year = Qnil, subsec = Qnil; int mon = -1, mday = -1, hour = -1, min = -1, sec = -1; size_t ndigits; + size_t prec = NIL_P(precision) ? SIZE_MAX : NUM2SIZET(precision); while ((ptr < end) && ISSPACE(*ptr)) ptr++; year = parse_int(ptr, end, &ptr, &ndigits, true); @@ -2523,7 +2549,7 @@ time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone) expect_two_digits(sec); if (peek('.')) { ptr++; - for (ndigits = 0; ndigits < 45 && ISDIGIT(peekc_n(ndigits)); ++ndigits); + for (ndigits = 0; ndigits < prec && ISDIGIT(peekc_n(ndigits)); ++ndigits); if (!ndigits) { int clen = rb_enc_precise_mbclen(ptr, end, rb_enc_get(str)); if (clen < 0) clen = 0; @@ -2564,14 +2590,19 @@ time_init_parse(rb_execution_context_t *ec, VALUE time, VALUE str, VALUE zone) } } -#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); + struct vtm vtm = { + .wday = VTM_WDAY_INITVAL, + .yday = 0, + .zone = str_empty, + .year = year, + .mon = (mon < 0) ? 1 : mon, + .mday = (mday < 0) ? 1 : mday, + .hour = (hour < 0) ? 0 : hour, + .min = (min < 0) ? 0 : min, + .sec = (sec < 0) ? 0 : sec, + .subsecx = NIL_P(subsec) ? INT2FIX(0) : subsec, + }; + return time_init_vtm(klass, vtm, zone); } static void @@ -300,6 +300,8 @@ class Time # 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 + # 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.56789', precision: 3) # => 2000-12-31 23:59:59.567 -0600 # # With one to six arguments, returns a new \Time object # based on the given arguments, in the local timezone. @@ -375,7 +377,12 @@ class Time # Time.new(in: '-12:00') # # => 2022-08-23 08:49:26.1941467 -1200 # - def initialize(year = (now = true), mon = (str = year; nil), mday = nil, hour = nil, min = nil, sec = nil, zone = nil, in: nil) + # - +precision+: maximum effective digits in sub-second part, default is 9. + # More digits will be truncated, as other operations of \Time. + # Ignored unless the first argument is a string. + # + def initialize(year = (now = true), mon = (str = year; nil), mday = nil, hour = nil, min = nil, sec = nil, zone = nil, + in: nil, precision: 9) if zone if Primitive.arg!(:in) raise ArgumentError, "timezone argument given as positional and keyword arguments" @@ -388,10 +395,10 @@ class Time return Primitive.time_init_now(zone) end - if str and Primitive.time_init_parse(str, zone) + if str and Primitive.time_init_parse(str, zone, precision) return self end - Primitive.time_init_args(year, mon, mday, hour, min, sec, nil, zone) + Primitive.time_init_args(year, mon, mday, hour, min, sec, zone) end end |