[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(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")
}

79
time.c
View File

@ -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

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 +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