From d52f046f54f4283a2687356cb90879002fab73e1 Mon Sep 17 00:00:00 2001 From: Daniel Ritz Date: Fri, 18 Jan 2019 20:17:23 +0100 Subject: [PATCH] Fix time precision with fractional seconds as Rational Fixes #5558 --- core/src/main/java/org/jruby/RubyTime.java | 25 ++++++++++++++++------ test/jruby/test_time.rb | 15 +++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyTime.java b/core/src/main/java/org/jruby/RubyTime.java index 1baf241a262..e38751ec43c 100644 --- a/core/src/main/java/org/jruby/RubyTime.java +++ b/core/src/main/java/org/jruby/RubyTime.java @@ -1743,13 +1743,26 @@ private RubyTime initTime(ThreadContext context, IRubyObject[] args, boolean gmt // 1.9 will observe fractional seconds *if* not given usec if (args[5] != context.nil && args[6] == context.nil) { - double secs = RubyFloat.num2dbl(context, args[5]); - if (secs < 0 || secs >= TIME_SCALE) { - throw runtime.newArgumentError("argument out of range."); + if (args[5] instanceof RubyRational) { + RubyRational rat = (RubyRational) args[5]; + if (rat.isNegative()) { + throw runtime.newArgumentError("argument out of range."); + } + RubyRational nsec = (RubyRational) rat.op_mul(context, runtime.newFixnum(1_000_000_000)); + long full_nanos = nsec.getLongValue(); + long millis = full_nanos / 1_000_000; + + nanos = full_nanos - millis * 1_000_000; + instant = chrono.millis().add(instant, millis % 1000); + } else { + double secs = RubyFloat.num2dbl(context, args[5]); + if (secs < 0 || secs >= TIME_SCALE) { + throw runtime.newArgumentError("argument out of range."); + } + int int_millis = (int) (secs * 1000) % 1000; + instant = chrono.millis().add(instant, int_millis); + nanos = ((long) (secs * 1000000000) % 1000000); } - int int_millis = (int) (secs * 1000) % 1000; - instant = chrono.millis().add(instant, int_millis); - nanos = ((long) (secs * 1000000000) % 1000000); } dt = dt.withMillis(instant); diff --git a/test/jruby/test_time.rb b/test/jruby/test_time.rb index c320e2f4c29..388d40ea2e0 100644 --- a/test/jruby/test_time.rb +++ b/test/jruby/test_time.rb @@ -46,6 +46,21 @@ def test_nsec_rounding # GH-843 assert_false t1 == t2 end + def test_usec_as_rational + # 8.123456 is known to fail with Rational.to_f + sec = 8 + Rational(123456, 1_000_000) + t1 = Time.utc(2019,1,18,19,37, sec) + assert_equal 8, t1.sec + assert_equal 123456000, t1.nsec + end + + def test_nsec_as_rational + sec = 8 + Rational(123456789, 1_000_000_000) + t1 = Time.utc(2019,1,18,19,37, sec) + assert_equal 8, t1.sec + assert_equal 123456789, t1.nsec + end + def test_large_add # GH-1779 t = Time.local(2000, 1, 1) + (400 * 366 * 24 * 60 * 60) assert_equal 2400, t.year