summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/ruby/test_time.rb3
-rw-r--r--time.c79
-rw-r--r--timev.rb13
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")
}
diff --git a/time.c b/time.c
index d37ef8f728..bd147a1878 100644
--- a/time.c
+++ b/time.c
@@ -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
diff --git a/timev.rb b/timev.rb
index 1370a795a6..76d3ab7058 100644
--- a/timev.rb
+++ b/timev.rb
@@ -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