[Feature #18033] Add precision: option

Which limits the precision of subsecond. Defaulted to 9, that
means nanosecond.
This commit is contained in:
Nobuyoshi Nakada 2021-10-23 23:32:25 +09:00
parent 67c589afa0
commit 9515179d74
Notes: git 2022-12-16 13:53:19 +00:00
3 changed files with 68 additions and 27 deletions

View File

@ -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(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.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. ") { assert_raise_with_message(ArgumentError, "subsecond expected after dot: 00:56:17. ") {
Time.new("2020-12-25 00:56:17. +0900") Time.new("2020-12-25 00:56:17. +0900")
} }

79
time.c
View File

@ -515,17 +515,19 @@ wmod(wideval_t wx, wideval_t wy)
} }
static VALUE static VALUE
num_exact(VALUE v) num_exact_check(VALUE v)
{ {
VALUE tmp; VALUE tmp;
switch (TYPE(v)) { switch (TYPE(v)) {
case T_FIXNUM: case T_FIXNUM:
case T_BIGNUM: case T_BIGNUM:
return v; tmp = v;
break;
case T_RATIONAL: case T_RATIONAL:
return rb_rational_canonicalize(v); tmp = rb_rational_canonicalize(v);
break;
default: default:
if (!UNDEF_P(tmp = rb_check_funcall(v, idTo_r, 0, NULL))) { if (!UNDEF_P(tmp = rb_check_funcall(v, idTo_r, 0, NULL))) {
@ -535,10 +537,11 @@ num_exact(VALUE v)
/* FALLTHROUGH */ /* FALLTHROUGH */
} }
else if (RB_INTEGER_TYPE_P(tmp)) { else if (RB_INTEGER_TYPE_P(tmp)) {
return tmp; break;
} }
else if (RB_TYPE_P(tmp, T_RATIONAL)) { 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))) { else if (!NIL_P(tmp = rb_check_to_int(v))) {
@ -547,9 +550,26 @@ num_exact(VALUE v)
case T_NIL: case T_NIL:
case T_STRING: case T_STRING:
rb_raise(rb_eTypeError, "can't convert %"PRIsVALUE" into an exact number", return Qnil;
rb_obj_class(v));
} }
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 */ /* time_t */
@ -2349,13 +2369,13 @@ vtm_day_wraparound(struct vtm *vtm)
vtm_add_day(vtm, 1); vtm_add_day(vtm, 1);
} }
static VALUE time_init_vtm(VALUE time, struct vtm vtm, VALUE zone);
static VALUE static VALUE
time_init_args(rb_execution_context_t *ec, VALUE time, VALUE year, VALUE mon, VALUE mday, 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; struct vtm vtm;
VALUE utc = Qnil;
struct time_object *tobj;
vtm.wday = VTM_WDAY_INITVAL; vtm.wday = VTM_WDAY_INITVAL;
vtm.yday = 0; 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.sec = 0;
vtm.subsecx = INT2FIX(0); vtm.subsecx = INT2FIX(0);
} }
else if (!NIL_P(subsec)) {
vtm.sec = obj2ubits(sec, 6);
vtm.subsecx = subsec;
}
else { else {
VALUE subsecx; VALUE subsecx;
vtm.sec = obj2subsecx(sec, &subsecx); vtm.sec = obj2subsecx(sec, &subsecx);
vtm.subsecx = 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.isdst = VTM_ISDST_INITVAL;
vtm.utc_offset = Qnil; vtm.utc_offset = Qnil;
const VALUE arg = zone; const VALUE arg = zone;
@ -2474,7 +2499,7 @@ parse_int(const char *ptr, const char *end, const char **endp, size_t *ndigits,
} }
static VALUE 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 (NIL_P(str = rb_check_string_type(str))) return Qnil;
if (!rb_enc_str_asciicompat_p(str)) { 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; VALUE year = Qnil, subsec = Qnil;
int mon = -1, mday = -1, hour = -1, min = -1, sec = -1; int mon = -1, mday = -1, hour = -1, min = -1, sec = -1;
size_t ndigits; size_t ndigits;
size_t prec = NIL_P(precision) ? SIZE_MAX : NUM2SIZET(precision);
while ((ptr < end) && ISSPACE(*ptr)) ptr++; while ((ptr < end) && ISSPACE(*ptr)) ptr++;
year = parse_int(ptr, end, &ptr, &ndigits, true); 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); expect_two_digits(sec);
if (peek('.')) { if (peek('.')) {
ptr++; ptr++;
for (ndigits = 0; ndigits < 45 && ISDIGIT(peekc_n(ndigits)); ++ndigits); for (ndigits = 0; ndigits < prec && ISDIGIT(peekc_n(ndigits)); ++ndigits);
if (!ndigits) { if (!ndigits) {
int clen = rb_enc_precise_mbclen(ptr, end, rb_enc_get(str)); int clen = rb_enc_precise_mbclen(ptr, end, rb_enc_get(str));
if (clen < 0) clen = 0; 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)) struct vtm vtm = {
return time_init_args(ec, time, year, .wday = VTM_WDAY_INITVAL,
non_negative(mon), .yday = 0,
non_negative(mday), .zone = str_empty,
non_negative(hour), .year = year,
non_negative(min), .mon = (mon < 0) ? 1 : mon,
non_negative(sec), .mday = (mday < 0) ? 1 : mday,
subsec, zone); .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 static void

View File

@ -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') # => 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 +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', 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 # With one to six arguments, returns a new \Time object
# based on the given arguments, in the local timezone. # based on the given arguments, in the local timezone.
@ -375,7 +377,12 @@ class Time
# Time.new(in: '-12:00') # Time.new(in: '-12:00')
# # => 2022-08-23 08:49:26.1941467 -1200 # # => 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 zone
if Primitive.arg!(:in) if Primitive.arg!(:in)
raise ArgumentError, "timezone argument given as positional and keyword arguments" raise ArgumentError, "timezone argument given as positional and keyword arguments"
@ -388,10 +395,10 @@ class Time
return Primitive.time_init_now(zone) return Primitive.time_init_now(zone)
end end
if str and Primitive.time_init_parse(str, zone) if str and Primitive.time_init_parse(str, zone, precision)
return self return self
end 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
end end