Skip to content

Commit 449c612

Browse files
committed
Fix Time.new with String argument and handle nanoseconds correctly
1 parent 84a2a3a commit 449c612

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ New features:
66
Bug fixes:
77

88
* Fix `Range#cover?` on begin-less ranges and non-integer values (@nirvdrum, @rwstauner).
9+
* Fix `Time.new` with String argument and handle nanoseconds correctly (#3836, @andrykonchin).
910

1011
Compatibility:
1112

spec/ruby/core/time/new_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,36 @@ def obj.to_int; 3; end
524524
Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3r).subsec.should == 0.123r
525525
end
526526

527+
it "returns Time with correct subseconds when given seconds fraction is shorted than 6 digits" do
528+
Time.new("2020-12-25T00:56:17.123 +09:00").nsec.should == 123000000
529+
Time.new("2020-12-25T00:56:17.123 +09:00").usec.should == 123000
530+
Time.new("2020-12-25T00:56:17.123 +09:00").subsec.should == 0.123
531+
end
532+
533+
it "returns Time with correct subseconds when given seconds fraction is milliseconds" do
534+
Time.new("2020-12-25T00:56:17.123456 +09:00").nsec.should == 123456000
535+
Time.new("2020-12-25T00:56:17.123456 +09:00").usec.should == 123456
536+
Time.new("2020-12-25T00:56:17.123456 +09:00").subsec.should == 0.123456
537+
end
538+
539+
it "returns Time with correct subseconds when given seconds fraction is longer that 6 digits but shorted than 9 digits" do
540+
Time.new("2020-12-25T00:56:17.12345678 +09:00").nsec.should == 123456780
541+
Time.new("2020-12-25T00:56:17.12345678 +09:00").usec.should == 123456
542+
Time.new("2020-12-25T00:56:17.12345678 +09:00").subsec.should == 0.12345678
543+
end
544+
545+
it "returns Time with correct subseconds when given seconds fraction is nanoseconds" do
546+
Time.new("2020-12-25T00:56:17.123456789 +09:00").nsec.should == 123456789
547+
Time.new("2020-12-25T00:56:17.123456789 +09:00").usec.should == 123456
548+
Time.new("2020-12-25T00:56:17.123456789 +09:00").subsec.should == 0.123456789
549+
end
550+
551+
it "returns Time with correct subseconds when given seconds fraction is longer than 9 digits" do
552+
Time.new("2020-12-25T00:56:17.123456789876 +09:00").nsec.should == 123456789
553+
Time.new("2020-12-25T00:56:17.123456789876 +09:00").usec.should == 123456
554+
Time.new("2020-12-25T00:56:17.123456789876 +09:00").subsec.should == 0.123456789
555+
end
556+
527557
ruby_version_is ""..."3.3" do
528558
it "raise TypeError is can't convert precision keyword argument into Integer" do
529559
-> {

src/main/java/org/truffleruby/core/time/TimeNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ RubyTime timeSFromArray(
532532
Object utcoffset) {
533533
final RubyLanguage language = getLanguage();
534534

535-
if (nsec < 0 || nsec > 999999999 ||
535+
if (nsec < 0 || nsec > 999_999_999 ||
536536
sec < 0 || sec > 60 || // MRI accepts sec=60, whether it is a leap second or not
537537
min < 0 || min > 59 ||
538538
hour < 0 || hour > 23 ||

src/main/ruby/truffleruby/core/truffle/time_operations.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def self.compose(time_class, utc_offset, p1, p2 = nil, p3 = nil, p4 = nil, p5 =
9292
Primitive.time_s_from_array(time_class, sec, min, hour, mday, month, year, nsec, is_dst, is_utc, utc_offset)
9393
end
9494

95+
# MRI: time_init_parse()
9596
def self.new_from_string(time_class, str, **options)
9697
raise ArgumentError, 'time string should have ASCII compatible encoding' unless str.encoding.ascii_compatible?
9798

@@ -103,9 +104,23 @@ def self.new_from_string(time_class, str, **options)
103104
[ T] (?<hour> \d{2})
104105
: (?<min> \d{2})
105106
: (?<sec> \d{2})
106-
(?:\. (?<usec> \d+) )?
107+
(?:\. (?<subsec> \d+) )?
107108
(?:\s* (?<offset>\S+))?
108109
)?\z/x =~ str
110+
111+
# convert seconds fraction to milliseconds
112+
usec = if subsec
113+
ndigits = subsec.length
114+
115+
if ndigits <= 6
116+
subsec.to_i * 10.pow(6 - ndigits)
117+
else
118+
subsec.to_r / 10.pow(ndigits - 6) # convert to Rational to not loose precision
119+
end
120+
else
121+
nil
122+
end
123+
109124
return self.compose(time_class, self.utc_offset_for_compose(offset || options[:in]), year, month, mday, hour, min, sec, usec)
110125
end
111126

0 commit comments

Comments
 (0)