Skip to content

Commit 0122523

Browse files
committed
[GR-40333] Ruby 3.1 Support Time.new :in keyword argument
PullRequest: truffleruby/3683
2 parents 9bba23c + 391a81f commit 0122523

File tree

13 files changed

+136
-143
lines changed

13 files changed

+136
-143
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Compatibility:
8585
* `Fiber.current` and `Fiber#transfer` are available without `require 'fiber'` like in CRuby 3.1 (#2733, @eregon).
8686
* Add `freeze` keyword argument to `Marshal.load` (#2733, @andrykonchin).
8787
* Add `Integer.try_convert` (#2733, @moste00, @eregon).
88+
* Support optional `:in` keyword argument for `Time.now` and `Time.new` (#2733, @andrykonchin).
8889

8990
Performance:
9091

spec/ruby/core/time/new_spec.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ def zone.local_to_utc(t)
431431
time.zone.should == nil
432432
end
433433

434+
it "returns a Time with UTC offset specified as a single letter military timezone" do
435+
Time.new(2000, 1, 1, 0, 0, 0, in: "W").utc_offset.should == 3600 * -10
436+
end
437+
434438
it "could be a timezone object" do
435439
zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
436440
time = Time.new(2000, 1, 1, 12, 0, 0, in: zone)
@@ -445,13 +449,29 @@ def zone.local_to_utc(t)
445449
time.zone.should == zone
446450
end
447451

452+
it "allows omitting minor arguments" do
453+
Time.new(2000, 1, 1, 12, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 1, "+05:00")
454+
Time.new(2000, 1, 1, 12, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 0, "+05:00")
455+
Time.new(2000, 1, 1, 12, in: "+05:00").should == Time.new(2000, 1, 1, 12, 0, 0, "+05:00")
456+
Time.new(2000, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00")
457+
Time.new(2000, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00")
458+
Time.new(2000, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00")
459+
Time.new(in: "+05:00").round(0).should == Time.now.getlocal("+05:00").round(0)
460+
end
461+
462+
it "converts to a provided timezone if all the positional arguments are omitted" do
463+
Time.new(in: "+05:00").utc_offset.should == 5*3600
464+
end
465+
448466
it "raises ArgumentError if format is invalid" do
449467
-> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should raise_error(ArgumentError)
450468
-> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should raise_error(ArgumentError)
451469
end
452470

453471
it "raises ArgumentError if two offset arguments are given" do
454-
-> { Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") }.should raise_error(ArgumentError)
472+
-> {
473+
Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00")
474+
}.should raise_error(ArgumentError, "timezone argument given as positional and keyword arguments")
455475
end
456476
end
457477
end

spec/ruby/core/time/now_spec.rb

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,54 @@
44
describe "Time.now" do
55
it_behaves_like :time_now, :now
66

7-
describe ":in keyword argument" do
8-
it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
9-
time = Time.now(in: "+05:00")
7+
ruby_version_is '3.1' do # https://bugs.ruby-lang.org/issues/17485
8+
describe ":in keyword argument" do
9+
it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do
10+
time = Time.now(in: "+05:00")
1011

11-
time.utc_offset.should == 5*60*60
12-
time.zone.should == nil
12+
time.utc_offset.should == 5*60*60
13+
time.zone.should == nil
1314

14-
time = Time.now(in: "-09:00")
15+
time = Time.now(in: "-09:00")
1516

16-
time.utc_offset.should == -9*60*60
17-
time.zone.should == nil
18-
end
17+
time.utc_offset.should == -9*60*60
18+
time.zone.should == nil
19+
end
1920

20-
it "could be UTC offset as a number of seconds" do
21-
time = Time.now(in: 5*60*60)
21+
it "could be UTC offset as a number of seconds" do
22+
time = Time.now(in: 5*60*60)
2223

23-
time.utc_offset.should == 5*60*60
24-
time.zone.should == nil
24+
time.utc_offset.should == 5*60*60
25+
time.zone.should == nil
2526

26-
time = Time.now(in: -9*60*60)
27+
time = Time.now(in: -9*60*60)
2728

28-
time.utc_offset.should == -9*60*60
29-
time.zone.should == nil
30-
end
29+
time.utc_offset.should == -9*60*60
30+
time.zone.should == nil
31+
end
3132

32-
it "could be a timezone object" do
33-
zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
34-
time = Time.now(in: zone)
33+
it "returns a Time with UTC offset specified as a single letter military timezone" do
34+
Time.now(in: "W").utc_offset.should == 3600 * -10
35+
end
3536

36-
time.utc_offset.should == 5*3600+30*60
37-
time.zone.should == zone
37+
it "could be a timezone object" do
38+
zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo")
39+
time = Time.now(in: zone)
3840

39-
zone = TimeSpecs::TimezoneWithName.new(name: "PST")
40-
time = Time.now(in: zone)
41+
time.utc_offset.should == 5*3600+30*60
42+
time.zone.should == zone
4143

42-
time.utc_offset.should == -9*60*60
43-
time.zone.should == zone
44-
end
44+
zone = TimeSpecs::TimezoneWithName.new(name: "PST")
45+
time = Time.now(in: zone)
46+
47+
time.utc_offset.should == -9*60*60
48+
time.zone.should == zone
49+
end
4550

46-
it "raises ArgumentError if format is invalid" do
47-
-> { Time.now(in: "+09:99") }.should raise_error(ArgumentError)
48-
-> { Time.now(in: "ABC") }.should raise_error(ArgumentError)
51+
it "raises ArgumentError if format is invalid" do
52+
-> { Time.now(in: "+09:99") }.should raise_error(ArgumentError)
53+
-> { Time.now(in: "ABC") }.should raise_error(ArgumentError)
54+
end
4955
end
5056
end
5157
end

spec/ruby/core/time/utc_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,22 @@
2121
Time.new(2022, 1, 1, 0, 0, 0, "UTC").utc?.should == true
2222
Time.now.localtime("UTC").utc?.should == true
2323
Time.at(Time.now, in: 'UTC').utc?.should == true
24+
25+
ruby_version_is "3.1" do
26+
Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").utc?.should == true
27+
Time.now(in: "UTC").utc?.should == true
28+
end
2429
end
2530

2631
it "does treat time with Z offset as UTC" do
2732
Time.new(2022, 1, 1, 0, 0, 0, "Z").utc?.should == true
2833
Time.now.localtime("Z").utc?.should == true
2934
Time.at(Time.now, in: 'Z').utc?.should == true
35+
36+
ruby_version_is "3.1" do
37+
Time.new(2022, 1, 1, 0, 0, 0, in: "Z").utc?.should == true
38+
Time.now(in: "Z").utc?.should == true
39+
end
3040
end
3141

3242
ruby_version_is "3.1" do

spec/ruby/core/time/zone_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@
7474
Time.now.localtime("-00:00").zone.should == "UTC"
7575
Time.at(Time.now, in: '-00:00').zone.should == "UTC"
7676
end
77+
78+
ruby_version_is "3.1" do
79+
Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").zone.should == "UTC"
80+
Time.new(2022, 1, 1, 0, 0, 0, in: "Z").zone.should == "UTC"
81+
82+
Time.now(in: 'UTC').zone.should == "UTC"
83+
Time.now(in: 'Z').zone.should == "UTC"
84+
85+
Time.at(Time.now, in: 'UTC').zone.should == "UTC"
86+
Time.at(Time.now, in: 'Z').zone.should == "UTC"
87+
end
7788
end
7889

7990
platform_is_not :aix, :windows do

spec/tags/core/time/at_tags.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
fails:Time.at passed Numeric roundtrips a Rational produced by #to_r
21
fails:Time.at :in keyword argument could be a timezone object
32
fails:Time.at passed non-Time, non-Numeric with an argument that responds to #to_r needs for the argument to respond to #to_int too
4-
fails:Time.at :in keyword argument raises ArgumentError if format is invalid

spec/tags/core/time/new_tags.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,4 @@ fails:Time.new with a timezone argument #name method cannot marshal Time if #nam
1919
fails:Time.new with a timezone argument subject's class implements .find_timezone method calls .find_timezone to build a time object at loading marshaled data
2020
fails:Time.new with a timezone argument subject's class implements .find_timezone method calls .find_timezone to build a time object if passed zone name as a timezone argument
2121
fails:Time.new with a timezone argument subject's class implements .find_timezone method does not call .find_timezone if passed any not string/numeric/timezone timezone argument
22-
fails:Time.new has at least microsecond precision
23-
fails:Time.new with a timezone argument :in keyword argument could be UTC offset as a String in '+HH:MM or '-HH:MM' format
24-
fails:Time.new with a timezone argument :in keyword argument could be UTC offset as a number of seconds
2522
fails:Time.new with a timezone argument :in keyword argument could be a timezone object
26-
fails:Time.new with a timezone argument :in keyword argument raises ArgumentError if format is invalid

spec/tags/core/time/now_tags.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
fails:Time.now :in keyword argument could be UTC offset as a String in '+HH:MM or '-HH:MM' format
2-
fails:Time.now :in keyword argument could be UTC offset as a number of seconds
31
fails:Time.now :in keyword argument could be a timezone object

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ private ZonedDateTime inUTC(ZonedDateTime dateTime) {
197197

198198
}
199199

200-
@CoreMethod(names = "now", constructor = true)
201-
public abstract static class TimeNowNode extends CoreMethodArrayArgumentsNode {
200+
@Primitive(name = "time_now")
201+
public abstract static class TimeNowNode extends PrimitiveArrayArgumentsNode {
202202

203203
@Child private GetTimeZoneNode getTimeZoneNode = GetTimeZoneNodeGen.create();
204204
@Child private TruffleString.FromJavaStringNode fromJavaStringNode = TruffleString.FromJavaStringNode.create();

src/main/ruby/truffleruby/core/time.rb

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -464,15 +464,17 @@ def compose(offset, p1, p2=nil, p3=nil, p4=nil, p5=nil, p6=nil, p7=nil,
464464
end
465465
private :compose
466466

467-
def new(year=undefined, month=nil, day=nil, hour=nil, minute=nil, second=nil, utc_offset=nil)
467+
def new(year=undefined, month=nil, day=nil, hour=nil, minute=nil, second=nil, utc_offset=nil, **options)
468+
if utc_offset && options[:in]
469+
raise ArgumentError, 'timezone argument given as positional and keyword arguments'
470+
end
471+
472+
utc_offset ||= options[:in]
473+
468474
if Primitive.undefined?(year)
469-
self.now
475+
utc_offset ? self.now.getlocal(utc_offset) : self.now
470476
elsif Primitive.nil? utc_offset
471477
compose(:local, year, month, day, hour, minute, second)
472-
elsif utc_offset.instance_of?(String) && !utc_offset.encoding.ascii_compatible?
473-
raise ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: ' + utc_offset.inspect
474-
elsif utc_offset.instance_of?(String) && !valid_utc_offset_string?(utc_offset)
475-
raise ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: ' + utc_offset
476478
elsif utc_offset == :std
477479
compose(:local, second, minute, hour, day, month, year, nil, nil, false, nil)
478480
elsif utc_offset == :dst
@@ -487,19 +489,24 @@ def new(year=undefined, month=nil, day=nil, hour=nil, minute=nil, second=nil, ut
487489
end
488490
end
489491

490-
def valid_utc_offset_string?(utc_offset)
491-
utc_offset == 'UTC' \
492-
|| (utc_offset.size == 1 && ('A'..'Z') === utc_offset && utc_offset != 'J') \
493-
|| (utc_offset =~ /\A[+-](\d{2})(?::(\d{2})(?::(\d{2}))?)?\z/ && $1.to_i < 24 && $2.to_i < 60 && $3.to_i < 60) \
494-
|| (utc_offset =~ /\A[+-](\d{2})(?:(\d{2})(?:(\d{2}))?)?\z/ && $1.to_i < 24 && $2.to_i < 60 && $3.to_i < 60) # without ":" separators
495-
end
496-
private :valid_utc_offset_string?
497-
498492
def utc_offset_in_utc?(utc_offset)
499493
utc_offset == 'UTC' || utc_offset == 'Z' || utc_offset == '-00:00'
500494
end
501495
private :utc_offset_in_utc?
502496

497+
def now(**options)
498+
time_now = Primitive.time_now(self)
499+
in_timezone = options[:in]
500+
501+
if in_timezone
502+
utc_offset = Truffle::Type.coerce_to_utc_offset(in_timezone)
503+
is_utc = utc_offset_in_utc?(in_timezone)
504+
is_utc ? Primitive.time_utctime(time_now) : Primitive.time_localtime(time_now, utc_offset)
505+
else
506+
time_now
507+
end
508+
end
509+
503510
def local(*args)
504511
compose(:local, *args)
505512
end

0 commit comments

Comments
 (0)