Skip to content

Commit 682626e

Browse files
committedMar 27, 2018
[fix] cast nsec nanos to long to avoid "overflow" with double value
fixes #4989 ... follow-up on #4866 (4be8d66)
1 parent 1718c4a commit 682626e

File tree

2 files changed

+54
-18
lines changed

2 files changed

+54
-18
lines changed
 

Diff for: ‎core/src/main/java/org/jruby/RubyTime.java

+12-18
Original file line numberDiff line numberDiff line change
@@ -1549,22 +1549,17 @@ private static RubyTime createTime(ThreadContext context, RubyClass klass, IRuby
15491549
}
15501550

15511551
RubyTime time = new RubyTime(runtime, klass, dt);
1552-
// Ignores usec if 8 args (for compatibility with parsedate) or if not supplied.
1553-
if (args.length != 8 && !args[6].isNil()) {
1554-
boolean fractionalUSecGiven = args[6] instanceof RubyFloat || args[6] instanceof RubyRational;
1555-
1556-
if (fractionalUSecGiven) {
1557-
if (args[6] instanceof RubyRational) {
1558-
RubyRational usecRat = (RubyRational) args[6];
1559-
RubyRational nsecRat = (RubyRational) usecRat.op_mul(context, runtime.newFixnum(1000));
1560-
double tmpNanos = nsecRat.getDoubleValue(context);
1561-
time.dt = dt.withMillis((long) (dt.getMillis() + (tmpNanos / 1000000)));
1562-
nanos = (long) tmpNanos % 1000000;
1563-
} else {
1564-
double micros = RubyNumeric.num2dbl(args[6]);
1565-
time.dt = dt.withMillis(dt.getMillis() + (long) (micros / 1000));
1566-
nanos = (long) Math.rint((micros * 1000) % 1000000);
1567-
}
1552+
// Ignores usec if 8 args (for compatibility with parse-date) or if not supplied.
1553+
if (args.length != 8 && args[6] != context.nil) {
1554+
if (args[6] instanceof RubyRational) {
1555+
RubyRational nsec = (RubyRational) ((RubyRational) args[6]).op_mul(context, runtime.newFixnum(1000));
1556+
long tmpNanos = (long) nsec.getDoubleValue(context);
1557+
time.dt = dt.withMillis(dt.getMillis() + (tmpNanos / 1_000_000));
1558+
nanos = tmpNanos % 1_000_000;
1559+
} else if (args[6] instanceof RubyFloat) {
1560+
double micros = ((RubyFloat) args[6]).getDoubleValue();
1561+
time.dt = dt.withMillis(dt.getMillis() + (long) (micros / 1000));
1562+
nanos = (long) Math.rint((micros * 1000) % 1_000_000);
15681563
} else {
15691564
int usec = int_args[4] % 1000;
15701565
int msec = int_args[4] / 1000;
@@ -1578,8 +1573,7 @@ private static RubyTime createTime(ThreadContext context, RubyClass klass, IRuby
15781573
}
15791574
}
15801575

1581-
if (nanos != 0)
1582-
time.setNSec(nanos);
1576+
if (nanos != 0) time.setNSec(nanos);
15831577

15841578
time.callInit(IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
15851579
time.setIsTzRelative(setTzRelative);

Diff for: ‎test/jruby/test_time.rb

+42
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,48 @@ def test_far_future
5757
t2 = now + 90000000000
5858
assert_false t1 == t2
5959
end
60+
61+
def test_end_of_day
62+
time = time_change(Time.now,
63+
:hour => 23,
64+
:min => 59,
65+
:sec => 59,
66+
:usec => Rational(999_999_999, 1000)
67+
)
68+
69+
assert_equal 23, time.hour
70+
assert_equal 59, time.min
71+
assert_equal 59, time.sec
72+
assert_equal Time.now.zone, time.zone
73+
assert_equal 999999999, time.nsec
74+
end
75+
76+
def time_change(time, options) # from ActiveSupport
77+
new_year = options.fetch(:year, time.year)
78+
new_month = options.fetch(:month, time.month)
79+
new_day = options.fetch(:day, time.day)
80+
new_hour = options.fetch(:hour, time.hour)
81+
new_min = options.fetch(:min, options[:hour] ? 0 : time.min)
82+
new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : time.sec)
83+
84+
if new_nsec = options[:nsec]
85+
raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
86+
new_usec = Rational(new_nsec, 1000)
87+
else
88+
new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(time.nsec, 1000))
89+
end
90+
91+
if time.utc?
92+
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
93+
elsif time.zone
94+
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
95+
else
96+
raise ArgumentError, 'argument out of range' if new_usec >= 1000000
97+
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), time.utc_offset)
98+
end
99+
end
100+
private :time_change
101+
60102
end
61103

62104
class TestTimeNilOps < Test::Unit::TestCase

0 commit comments

Comments
 (0)