diff options
Diffstat (limited to 'spec/ruby/core/time')
77 files changed, 2007 insertions, 385 deletions
diff --git a/spec/ruby/core/time/_dump_spec.rb b/spec/ruby/core/time/_dump_spec.rb index bec16dab69..21f0806327 100644 --- a/spec/ruby/core/time/_dump_spec.rb +++ b/spec/ruby/core/time/_dump_spec.rb @@ -1,5 +1,5 @@ -# -*- encoding: binary -*- -require File.expand_path('../../../spec_helper', __FILE__) +# encoding: binary +require_relative '../../spec_helper' describe "Time#_dump" do before :each do @@ -10,22 +10,22 @@ describe "Time#_dump" do end it "is a private method" do - Time.should have_private_instance_method(:_dump, false) + Time.private_instance_methods(false).should.include?(:_dump) end # http://redmine.ruby-lang.org/issues/show/627 it "preserves the GMT flag" do - @t.gmt?.should == true + @t.should.gmt? dump = @t.send(:_dump).unpack("VV").first ((dump >> 30) & 0x1).should == 1 - @local.gmt?.should == false + @local.should_not.gmt? dump = @local.send(:_dump).unpack("VV").first ((dump >> 30) & 0x1).should == 0 end it "dumps a Time object to a bytestring" do - @s.should be_an_instance_of(String) + @s.should.instance_of?(String) @s.should == [3222863947, 2235564032].pack("VV") end @@ -53,4 +53,3 @@ describe "Time#_dump" do t.send(:_dump).should == "\364\001\031\200\313\000\020\004" end end - diff --git a/spec/ruby/core/time/_load_spec.rb b/spec/ruby/core/time/_load_spec.rb index 12fcb219ed..a74e3dc129 100644 --- a/spec/ruby/core/time/_load_spec.rb +++ b/spec/ruby/core/time/_load_spec.rb @@ -1,9 +1,9 @@ -# -*- encoding: binary -*- -require File.expand_path('../../../spec_helper', __FILE__) +# encoding: binary +require_relative '../../spec_helper' describe "Time._load" do it "is a private method" do - Time.should have_private_method(:_load, false) + Time.private_methods(false).should.include?(:_load) end # http://redmine.ruby-lang.org/issues/show/627 @@ -43,12 +43,9 @@ describe "Time._load" do t.to_s.should == "2010-10-22 16:57:48 UTC" end - with_feature :encoding do - it "treats the data as binary data" do - data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE" - data.force_encoding Encoding::UTF_8 - t = Marshal.load(data) - t.to_s.should == "2013-04-08 12:47:45 UTC" - end + it "treats the data as binary data" do + data = "\x04\bu:\tTime\r\fM\x1C\xC0\x00\x00\xD0\xBE".dup.force_encoding Encoding::UTF_8 + t = Marshal.load(data) + t.to_s.should == "2013-04-08 12:47:45 UTC" end end diff --git a/spec/ruby/core/time/asctime_spec.rb b/spec/ruby/core/time/asctime_spec.rb index 3303e06f21..a41ee531e4 100644 --- a/spec/ruby/core/time/asctime_spec.rb +++ b/spec/ruby/core/time/asctime_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/asctime', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/asctime' describe "Time#asctime" do - it_behaves_like(:time_asctime, :asctime) + it_behaves_like :time_asctime, :asctime end diff --git a/spec/ruby/core/time/at_spec.rb b/spec/ruby/core/time/at_spec.rb index 6883a8d074..10d4d36a68 100644 --- a/spec/ruby/core/time/at_spec.rb +++ b/spec/ruby/core/time/at_spec.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Time.at" do describe "passed Numeric" do @@ -13,13 +14,13 @@ describe "Time.at" do end it "returns a non-UTC Time" do - Time.at(1184027924).utc?.should == false + Time.at(1184027924).should_not.utc? end it "returns a subclass instance on a Time subclass" do c = Class.new(Time) t = c.at(0) - t.should be_an_instance_of(c) + t.should.instance_of?(c) end it "roundtrips a Rational produced by #to_r" do @@ -31,10 +32,17 @@ describe "Time.at" do t2.nsec.should == t.nsec end - describe "passed BigDecimal" do - it "doesn't round input value" do - require 'bigdecimal' - Time.at(BigDecimal.new('1.1')).to_f.should == 1.1 + describe "passed Rational" do + it "returns Time with correct microseconds" do + t = Time.at(Rational(1_486_570_508_539_759, 1_000_000)) + t.usec.should == 539_759 + t.nsec.should == 539_759_000 + end + + it "returns Time with correct nanoseconds" do + t = Time.at(Rational(1_486_570_508_539_759_123, 1_000_000_000)) + t.usec.should == 539_759 + t.nsec.should == 539_759_123 end end end @@ -48,33 +56,33 @@ describe "Time.at" do it "creates a dup time object with the value given by time" do t1 = Time.new t2 = Time.at(t1) - t1.object_id.should_not == t2.object_id + t1.should_not.equal? t2 end it "returns a UTC time if the argument is UTC" do t = Time.now.getgm - Time.at(t).utc?.should == true + Time.at(t).should.utc? end it "returns a non-UTC time if the argument is non-UTC" do t = Time.now - Time.at(t).utc?.should == false + Time.at(t).should_not.utc? end it "returns a subclass instance" do c = Class.new(Time) t = c.at(Time.now) - t.should be_an_instance_of(c) + t.should.instance_of?(c) end end describe "passed non-Time, non-Numeric" do it "raises a TypeError with a String argument" do - lambda { Time.at("0") }.should raise_error(TypeError) + -> { Time.at("0") }.should.raise(TypeError) end it "raises a TypeError with a nil argument" do - lambda { Time.at(nil) }.should raise_error(TypeError) + -> { Time.at(nil) }.should.raise(TypeError) end describe "with an argument that responds to #to_int" do @@ -91,6 +99,12 @@ describe "Time.at" do o.should_receive(:to_r).and_return(Rational(5, 2)) Time.at(o).should == Time.at(Rational(5, 2)) end + + it "needs for the argument to respond to #to_int too" do + o = mock('rational-but-no-to_int') + def o.to_r; Rational(5, 2) end + -> { Time.at(o) }.should.raise(TypeError, "can't convert MockObject into an exact number") + end end end @@ -126,75 +140,176 @@ describe "Time.at" do describe "passed [Integer, nil]" do it "raises a TypeError" do - lambda { Time.at(0, nil) }.should raise_error(TypeError) + -> { Time.at(0, nil) }.should.raise(TypeError) end end describe "passed [Integer, String]" do it "raises a TypeError" do - lambda { Time.at(0, "0") }.should raise_error(TypeError) + -> { Time.at(0, "0") }.should.raise(TypeError) end end describe "passed [Time, Integer]" do # #8173 it "raises a TypeError" do - lambda { Time.at(Time.now, 500000) }.should raise_error(TypeError) + -> { Time.at(Time.now, 500000) }.should.raise(TypeError) end end - ruby_version_is "2.5" do - describe "passed [Time, Numeric, format]" do - context ":nanosecond format" do - it "traits second argument as nanoseconds" do - Time.at(0, 123456789, :nanosecond).nsec.should == 123456789 - end + describe "passed [Time, Numeric, format]" do + context ":nanosecond format" do + it "treats second argument as nanoseconds" do + Time.at(0, 123456789, :nanosecond).nsec.should == 123456789 end + end - context ":nsec format" do - it "traits second argument as nanoseconds" do - Time.at(0, 123456789, :nsec).nsec.should == 123456789 - end + context ":nsec format" do + it "treats second argument as nanoseconds" do + Time.at(0, 123456789, :nsec).nsec.should == 123456789 end + end - context ":microsecond format" do - it "traits second argument as microseconds" do - Time.at(0, 123456, :microsecond).nsec.should == 123456000 - end + context ":microsecond format" do + it "treats second argument as microseconds" do + Time.at(0, 123456, :microsecond).nsec.should == 123456000 end + end - context ":usec format" do - it "traits second argument as microseconds" do - Time.at(0, 123456, :usec).nsec.should == 123456000 - end + context ":usec format" do + it "treats second argument as microseconds" do + Time.at(0, 123456, :usec).nsec.should == 123456000 end + end + + context ":millisecond format" do + it "treats second argument as milliseconds" do + Time.at(0, 123, :millisecond).nsec.should == 123000000 + end + end - context ":millisecond format" do - it "traits second argument as milliseconds" do - Time.at(0, 123, :millisecond).nsec.should == 123000000 - end + context "not supported format" do + it "raises ArgumentError" do + -> { Time.at(0, 123456, 2) }.should.raise(ArgumentError) + -> { Time.at(0, 123456, nil) }.should.raise(ArgumentError) + -> { Time.at(0, 123456, :invalid) }.should.raise(ArgumentError) end - context "not supported format" do - it "raises ArgumentError" do - ->() { Time.at(0, 123456, 2) }.should raise_error(ArgumentError) - ->() { Time.at(0, 123456, nil) }.should raise_error(ArgumentError) - ->() { Time.at(0, 123456, :invalid) }.should raise_error(ArgumentError) - end - - it "does not try to convert format to Symbol with #to_sym" do - format = "usec" - format.should_not_receive(:to_sym) - -> () { Time.at(0, 123456, format) }.should raise_error(ArgumentError) - end + it "does not try to convert format to Symbol with #to_sym" do + format = +"usec" + format.should_not_receive(:to_sym) + -> { Time.at(0, 123456, format) }.should.raise(ArgumentError) end + end + + it "supports Float second argument" do + Time.at(0, 123456789.500, :nanosecond).nsec.should == 123456789 + Time.at(0, 123456789.500, :nsec).nsec.should == 123456789 + Time.at(0, 123456.500, :microsecond).nsec.should == 123456500 + Time.at(0, 123456.500, :usec).nsec.should == 123456500 + Time.at(0, 123.500, :millisecond).nsec.should == 123500000 + end + end + + describe ":in keyword argument" do + before do + @epoch_time = Time.now.to_i + end + + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.at(@epoch_time, in: "+05:00") + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + time.to_i.should == @epoch_time + + time = Time.at(@epoch_time, in: "-09:00") + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + time.to_i.should == @epoch_time + + time = Time.at(@epoch_time, in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + time.to_i.should == @epoch_time + end + + it "could be UTC offset as a number of seconds" do + time = Time.at(@epoch_time, in: 5*60*60) + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + time.to_i.should == @epoch_time + + time = Time.at(@epoch_time, in: -9*60*60) + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + time.to_i.should == @epoch_time + end + + it "could be UTC offset as a 'UTC' String" do + time = Time.at(@epoch_time, in: "UTC") + + time.utc_offset.should == 0 + time.zone.should == "UTC" + time.to_i.should == @epoch_time + end + + it "could be UTC offset as a military zone A-Z" do + time = Time.at(@epoch_time, in: "B") + + time.utc_offset.should == 3600 * 2 + time.zone.should == nil + time.to_i.should == @epoch_time + end + + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.at(@epoch_time, in: zone) + + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone + time.to_i.should == @epoch_time + + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.at(@epoch_time, in: zone) + + time.utc_offset.should == -9*60*60 + time.zone.should == zone + time.to_i.should == @epoch_time + end + + it "raises ArgumentError if format is invalid" do + -> { Time.at(@epoch_time, in: "+09:99") }.should.raise(ArgumentError) + -> { Time.at(@epoch_time, in: "ABC") }.should.raise(ArgumentError) + end + + it "raises ArgumentError if hours greater than 23" do # TODO + -> { Time.at(@epoch_time, in: "+24:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+2400") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.at(@epoch_time, in: "+99:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.at(@epoch_time, in: "+9900") }.should.raise(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if minutes greater than 59" do # TODO + -> { Time.at(@epoch_time, in: "+00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.at(@epoch_time, in: "+0060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.at(@epoch_time, in: "+00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.at(@epoch_time, in: "+0099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if seconds greater than 59" do + -> { Time.at(@epoch_time, in: "+00:00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.at(@epoch_time, in: "+000060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') - it "supports Float second argument" do - Time.at(0, 123456789.500, :nanosecond).nsec.should == 123456789 - Time.at(0, 123456789.500, :nsec).nsec.should == 123456789 - Time.at(0, 123456.500, :microsecond).nsec.should == 123456500 - Time.at(0, 123456.500, :usec).nsec.should == 123456500 - Time.at(0, 123.500, :millisecond).nsec.should == 123500000 + -> { Time.at(@epoch_time, in: "+00:00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.at(@epoch_time, in: "+000099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') end end end diff --git a/spec/ruby/core/time/ceil_spec.rb b/spec/ruby/core/time/ceil_spec.rb new file mode 100644 index 0000000000..18e26f9994 --- /dev/null +++ b/spec/ruby/core/time/ceil_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' + +describe "Time#ceil" do + before do + @time = Time.utc(2010, 3, 30, 5, 43, "25.0123456789".to_r) + end + + it "defaults to ceiling to 0 places" do + @time.ceil.should == Time.utc(2010, 3, 30, 5, 43, 26.to_r) + end + + it "ceils to 0 decimal places with an explicit argument" do + @time.ceil(0).should == Time.utc(2010, 3, 30, 5, 43, 26.to_r) + end + + it "ceils to 2 decimal places with an explicit argument" do + @time.ceil(2).should == Time.utc(2010, 3, 30, 5, 43, "25.02".to_r) + end + + it "ceils to 4 decimal places with an explicit argument" do + @time.ceil(4).should == Time.utc(2010, 3, 30, 5, 43, "25.0124".to_r) + end + + it "ceils to 7 decimal places with an explicit argument" do + @time.ceil(7).should == Time.utc(2010, 3, 30, 5, 43, "25.0123457".to_r) + end + + it "returns an instance of Time, even if #ceil is called on a subclass" do + subclass = Class.new(Time) + instance = subclass.at(0) + instance.class.should.equal? subclass + instance.ceil.should.instance_of?(Time) + end + + it "copies own timezone to the returning value" do + @time.zone.should == @time.ceil.zone + + time = with_timezone "JST-9" do + Time.at 0, 1 + end + + time.zone.should == time.ceil.zone + end +end diff --git a/spec/ruby/core/time/comparison_spec.rb b/spec/ruby/core/time/comparison_spec.rb index c5a5b83d28..0790088f9e 100644 --- a/spec/ruby/core/time/comparison_spec.rb +++ b/spec/ruby/core/time/comparison_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#<=>" do it "returns 1 if the first argument is a point in time after the second argument" do @@ -45,6 +45,42 @@ describe "Time#<=>" do (Time.at(100, 0) <=> Time.at(100, Rational(1,1000))).should == -1 end + it "returns nil when compared to an Integer because Time does not respond to #coerce" do + time = Time.at(1) + time.respond_to?(:coerce).should == false + time.should_receive(:respond_to?).exactly(2).and_return(false) + -> { + (time <=> 2).should == nil + (2 <=> time).should == nil + }.should_not complain + end + + context "given different timezones" do + it "returns 0 if time is the same as other" do + # three timezones, all at the same timestamp + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_cet = Time.new(2000, 1, 1, 1, 0, 0, '+01:00') + time_brt = Time.new(1999, 12, 31, 21, 0, 0, '-03:00') + (time_utc <=> time_cet).should == 0 + (time_utc <=> time_brt).should == 0 + (time_cet <=> time_brt).should == 0 + end + + it "returns -1 if the first argument is before the second argument" do + # time_brt is later, even though the date is earlier + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_brt = Time.new(1999, 12, 31, 23, 0, 0, '-03:00') + (time_utc <=> time_brt).should == -1 + end + + it "returns 1 if the first argument is before the second argument" do + # time_brt is later, even though the date is earlier + time_utc = Time.new(2000, 1, 1, 0, 0, 0, 0) + time_brt = Time.new(1999, 12, 31, 23, 0, 0, '-03:00') + (time_brt <=> time_utc).should == 1 + end + end + describe "given a non-Time argument" do it "returns nil if argument <=> self returns nil" do t = Time.now @@ -88,7 +124,7 @@ describe "Time#<=>" do def r.<=>(other); other <=> self; end r.should_receive(:<=>).once - (t <=> r).should be_nil + (t <=> r).should == nil end end end diff --git a/spec/ruby/core/time/ctime_spec.rb b/spec/ruby/core/time/ctime_spec.rb index cf9c1ee850..57e7cfd9ce 100644 --- a/spec/ruby/core/time/ctime_spec.rb +++ b/spec/ruby/core/time/ctime_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/asctime', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/asctime' describe "Time#ctime" do - it_behaves_like(:time_asctime, :ctime) + it_behaves_like :time_asctime, :ctime end diff --git a/spec/ruby/core/time/day_spec.rb b/spec/ruby/core/time/day_spec.rb index 8e77446070..895bcd7a86 100644 --- a/spec/ruby/core/time/day_spec.rb +++ b/spec/ruby/core/time/day_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/day', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/day' describe "Time#day" do - it_behaves_like(:time_day, :day) + it_behaves_like :time_day, :day end diff --git a/spec/ruby/core/time/deconstruct_keys_spec.rb b/spec/ruby/core/time/deconstruct_keys_spec.rb new file mode 100644 index 0000000000..9918728c1d --- /dev/null +++ b/spec/ruby/core/time/deconstruct_keys_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' + +describe "Time#deconstruct_keys" do + it "returns whole hash for nil as an argument" do + d = Time.utc(2022, 10, 5, 13, 30) + res = { year: 2022, month: 10, day: 5, yday: 278, wday: 3, hour: 13, + min: 30, sec: 0, subsec: 0, dst: false, zone: "UTC" } + d.deconstruct_keys(nil).should == res + end + + it "returns only specified keys" do + d = Time.utc(2022, 10, 5, 13, 39) + d.deconstruct_keys([:zone, :subsec]).should == { zone: "UTC", subsec: 0 } + end + + it "requires one argument" do + -> { + Time.new(2022, 10, 5, 13, 30).deconstruct_keys + }.should.raise(ArgumentError) + end + + it "it raises error when argument is neither nil nor array" do + d = Time.new(2022, 10, 5, 13, 30) + + -> { d.deconstruct_keys(1) }.should.raise(TypeError, "wrong argument type Integer (expected Array or nil)") + -> { d.deconstruct_keys("asd") }.should.raise(TypeError, "wrong argument type String (expected Array or nil)") + -> { d.deconstruct_keys(:x) }.should.raise(TypeError, "wrong argument type Symbol (expected Array or nil)") + -> { d.deconstruct_keys({}) }.should.raise(TypeError, "wrong argument type Hash (expected Array or nil)") + end + + it "returns {} when passed []" do + Time.new(2022, 10, 5, 13, 30).deconstruct_keys([]).should == {} + end + + it "ignores non-Symbol keys" do + Time.new(2022, 10, 5, 13, 30).deconstruct_keys(['year', []]).should == {} + end + + it "ignores not existing Symbol keys and processes keys after the first non-existing one" do + d = Time.utc(2022, 10, 5, 13, 30) + d.deconstruct_keys([:year, :a, :month, :b, :day]).should == { year: 2022, month: 10, day: 5 } + end +end diff --git a/spec/ruby/core/time/dst_spec.rb b/spec/ruby/core/time/dst_spec.rb index 05a0a213c5..436240aae5 100644 --- a/spec/ruby/core/time/dst_spec.rb +++ b/spec/ruby/core/time/dst_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/isdst', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/isdst' describe "Time#dst?" do - it_behaves_like(:time_isdst, :dst?) + it_behaves_like :time_isdst, :dst? end diff --git a/spec/ruby/core/time/dup_spec.rb b/spec/ruby/core/time/dup_spec.rb index b32ce96d44..33aa1304ef 100644 --- a/spec/ruby/core/time/dup_spec.rb +++ b/spec/ruby/core/time/dup_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#dup" do it "returns a Time object that represents the same time" do @@ -7,7 +7,7 @@ describe "Time#dup" do end it "copies the gmt state flag" do - Time.now.gmtime.dup.gmt?.should == true + Time.now.gmtime.dup.should.gmt? end it "returns an independent Time object" do @@ -15,32 +15,32 @@ describe "Time#dup" do t2 = t.dup t.gmtime - t2.gmt?.should == false + t2.should_not.gmt? end it "returns a subclass instance" do c = Class.new(Time) t = c.now - t.should be_an_instance_of(c) - t.dup.should be_an_instance_of(c) + t.should.instance_of?(c) + t.dup.should.instance_of?(c) end it "returns a clone of Time instance" do c = Time.dup t = c.now - t.should be_an_instance_of(c) - t.should_not be_an_instance_of(Time) + t.should.instance_of?(c) + t.should_not.instance_of?(Time) - t.dup.should be_an_instance_of(c) - t.dup.should_not be_an_instance_of(Time) + t.dup.should.instance_of?(c) + t.dup.should_not.instance_of?(Time) end it "does not copy frozen status from the original" do t = Time.now t.freeze t2 = t.dup - t2.frozen?.should be_false + t2.frozen?.should == false end end diff --git a/spec/ruby/core/time/eql_spec.rb b/spec/ruby/core/time/eql_spec.rb index af96c96cc3..b7505969dd 100644 --- a/spec/ruby/core/time/eql_spec.rb +++ b/spec/ruby/core/time/eql_spec.rb @@ -1,29 +1,29 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#eql?" do it "returns true if self and other have the same whole number of seconds" do - Time.at(100).should eql(Time.at(100)) + Time.at(100).should.eql?(Time.at(100)) end it "returns false if self and other have differing whole numbers of seconds" do - Time.at(100).should_not eql(Time.at(99)) + Time.at(100).should_not.eql?(Time.at(99)) end it "returns true if self and other have the same number of microseconds" do - Time.at(100, 100).should eql(Time.at(100, 100)) + Time.at(100, 100).should.eql?(Time.at(100, 100)) end it "returns false if self and other have differing numbers of microseconds" do - Time.at(100, 100).should_not eql(Time.at(100, 99)) + Time.at(100, 100).should_not.eql?(Time.at(100, 99)) end it "returns false if self and other have differing fractional microseconds" do - Time.at(100, Rational(100,1000)).should_not eql(Time.at(100, Rational(99,1000))) + Time.at(100, Rational(100,1000)).should_not.eql?(Time.at(100, Rational(99,1000))) end it "returns false when given a non-time value" do - Time.at(100, 100).should_not eql("100") - Time.at(100, 100).should_not eql(100) - Time.at(100, 100).should_not eql(100.1) + Time.at(100, 100).should_not.eql?("100") + Time.at(100, 100).should_not.eql?(100) + Time.at(100, 100).should_not.eql?(100.1) end end diff --git a/spec/ruby/core/time/fixtures/classes.rb b/spec/ruby/core/time/fixtures/classes.rb index 328f9160f6..21c4e1effb 100644 --- a/spec/ruby/core/time/fixtures/classes.rb +++ b/spec/ruby/core/time/fixtures/classes.rb @@ -9,4 +9,97 @@ module TimeSpecs end end + class Timezone + def initialize(options) + @offset = options[:offset] + end + + def local_to_utc(t) + t - @offset + end + + def utc_to_local(t) + t + @offset + end + end + + class TimezoneMethodCallRecorder < Timezone + def initialize(options, &blk) + super(options) + @blk = blk + end + + def local_to_utc(t) + @blk.call(t) + super + end + + def utc_to_local(t) + @blk.call(t) + super + end + end + + class TimeLikeArgumentRecorder + def self.result + arguments = [] + + zone = TimeSpecs::TimezoneMethodCallRecorder.new(offset: 0) do |obj| + arguments << obj + end + + # ensure timezone's methods are called at least once + Time.new(2000, 1, 1, 12, 0, 0, zone) + + return arguments[0] + end + end + + Z = Struct.new(:offset, :abbr) + Zone = Struct.new(:std, :dst, :dst_range) + Zones = { + "Asia/Colombo" => Zone[Z[5*3600+30*60, "MMT"], nil, nil], + "PST" => Zone[Z[(-9*60*60), "PST"], nil, nil], + } + + class TimezoneWithName < Timezone + attr_reader :name + + def initialize(options) + @name = options[:name] + @std, @dst, @dst_range = *Zones[@name] + end + + def dst?(t) + @dst_range&.cover?(t.mon) + end + + def zone(t) + (dst?(t) ? @dst : @std) + end + + def utc_offset(t) + zone(t)&.offset || 0 + end + + def abbr(t) + zone(t)&.abbr + end + + def local_to_utc(t) + t - utc_offset(t) + end + + def utc_to_local(t) + t + utc_offset(t) + end + end + + class TimeWithFindTimezone < Time + def self.find_timezone(name) + TimezoneWithName.new(name: name.to_s) + end + end + + TimezoneWithAbbr = TimezoneWithName end diff --git a/spec/ruby/core/time/floor_spec.rb b/spec/ruby/core/time/floor_spec.rb new file mode 100644 index 0000000000..41e5142b19 --- /dev/null +++ b/spec/ruby/core/time/floor_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +describe "Time#floor" do + before do + @time = Time.utc(2010, 3, 30, 5, 43, "25.123456789".to_r) + end + + it "defaults to flooring to 0 places" do + @time.floor.should == Time.utc(2010, 3, 30, 5, 43, 25.to_r) + end + + it "floors to 0 decimal places with an explicit argument" do + @time.floor(0).should == Time.utc(2010, 3, 30, 5, 43, 25.to_r) + end + + it "floors to 7 decimal places with an explicit argument" do + @time.floor(7).should == Time.utc(2010, 3, 30, 5, 43, "25.1234567".to_r) + end + + it "returns an instance of Time, even if #floor is called on a subclass" do + subclass = Class.new(Time) + instance = subclass.at(0) + instance.class.should.equal? subclass + instance.floor.should.instance_of?(Time) + end + + it "copies own timezone to the returning value" do + @time.zone.should == @time.floor.zone + + time = with_timezone "JST-9" do + Time.at 0, 1 + end + + time.zone.should == time.floor.zone + end +end diff --git a/spec/ruby/core/time/friday_spec.rb b/spec/ruby/core/time/friday_spec.rb index d38a261080..8bee7f7558 100644 --- a/spec/ruby/core/time/friday_spec.rb +++ b/spec/ruby/core/time/friday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#friday?" do it "returns true if time represents Friday" do - Time.local(2000, 1, 7).friday?.should == true + Time.local(2000, 1, 7).should.friday? end it "returns false if time doesn't represent Friday" do - Time.local(2000, 1, 1).friday?.should == false + Time.local(2000, 1, 1).should_not.friday? end end diff --git a/spec/ruby/core/time/getgm_spec.rb b/spec/ruby/core/time/getgm_spec.rb index f091b5c493..b5d45b1d9f 100644 --- a/spec/ruby/core/time/getgm_spec.rb +++ b/spec/ruby/core/time/getgm_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/getgm', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/getgm' describe "Time#getgm" do - it_behaves_like(:time_getgm, :getgm) + it_behaves_like :time_getgm, :getgm end diff --git a/spec/ruby/core/time/getlocal_spec.rb b/spec/ruby/core/time/getlocal_spec.rb index a94d7f751b..7e5334c303 100644 --- a/spec/ruby/core/time/getlocal_spec.rb +++ b/spec/ruby/core/time/getlocal_spec.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Time#getlocal" do it "returns a new time which is the local representation of time" do @@ -13,6 +14,7 @@ describe "Time#getlocal" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(3630) t.should == Time.new(2007, 1, 9, 13, 0, 30, 3630) t.utc_offset.should == 3630 + t.zone.should == nil end platform_is_not :windows do @@ -39,7 +41,7 @@ describe "Time#getlocal" do it "returns a Time with a UTC offset of the specified number of Rational seconds" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(Rational(7201, 2)) t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2)) - t.utc_offset.should eql(Rational(7201, 2)) + t.utc_offset.should.eql?(Rational(7201, 2)) end describe "with an argument that responds to #to_r" do @@ -48,7 +50,7 @@ describe "Time#getlocal" do o.should_receive(:to_r).and_return(Rational(7201, 2)) t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal(o) t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2)) - t.utc_offset.should eql(Rational(7201, 2)) + t.utc_offset.should.eql?(Rational(7201, 2)) end end @@ -58,12 +60,24 @@ describe "Time#getlocal" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00") t.should == Time.new(2007, 1, 9, 11, 0, 0, -3600) t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0).getlocal("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + describe "with an argument that responds to #to_str" do it "coerces using #to_str" do o = mock('string') @@ -76,23 +90,117 @@ describe "Time#getlocal" do it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do t = Time.now - lambda { t.getlocal("3600") }.should raise_error(ArgumentError) + -> { t.getlocal("3600") }.should.raise(ArgumentError) end it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do t = Time.now - lambda { t.getlocal("-01:00".encode("UTF-16LE")) }.should raise_error(ArgumentError) + -> { t.getlocal("-01:00".encode("UTF-16LE")) }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do t = Time.new t.getlocal(-86400 + 1).utc_offset.should == (-86400 + 1) - lambda { t.getlocal(-86400) }.should raise_error(ArgumentError) + -> { t.getlocal(-86400) }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do t = Time.new t.getlocal(86400 - 1).utc_offset.should == (86400 - 1) - lambda { t.getlocal(86400) }.should raise_error(ArgumentError) + -> { t.getlocal(86400) }.should.raise(ArgumentError) + end + + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now.getlocal("+24:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+2400") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.now.getlocal("+99:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now.getlocal("+9900") }.should.raise(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now.getlocal("+00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.getlocal("+0060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.getlocal("+00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.getlocal("+0099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.getlocal("+00:00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.getlocal("+000060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.getlocal("+00:00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.getlocal("+000099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + + describe "with a timezone argument" do + it "returns a Time in the timezone" do + zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) + time = Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone) + + time.zone.should == zone + time.utc_offset.should == 5*3600+30*60 + end + + it "accepts timezone argument that must have #local_to_utc and #utc_to_local methods" do + zone = Object.new + def zone.utc_to_local(time) + time + end + def zone.local_to_utc(time) + time + end + + -> { + Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone).should.is_a?(Time) + }.should_not.raise + end + + it "raises TypeError if timezone does not implement #utc_to_local method" do + zone = Object.new + def zone.local_to_utc(time) + time + end + + -> { + Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone) + }.should.raise(TypeError, /can't convert \w+ into an exact number/) + end + + it "does not raise exception if timezone does not implement #local_to_utc method" do + zone = Object.new + def zone.utc_to_local(time) + time + end + + -> { + Time.utc(2000, 1, 1, 12, 0, 0).getlocal(zone).should.is_a?(Time) + }.should_not.raise + end + + context "subject's class implements .find_timezone method" do + it "calls .find_timezone to build a time object if passed zone name as a timezone argument" do + time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0).getlocal("Asia/Colombo") + time.zone.should.is_a? TimeSpecs::TimezoneWithName + time.zone.name.should == "Asia/Colombo" + + time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0).getlocal("some invalid zone name") + time.zone.should.is_a? TimeSpecs::TimezoneWithName + time.zone.name.should == "some invalid zone name" + end + + it "does not call .find_timezone if passed any not string/numeric/timezone timezone argument" do + [Object.new, [], {}, :"some zone"].each do |zone| + time = TimeSpecs::TimeWithFindTimezone.utc(2000, 1, 1, 12, 0, 0) + + -> { + time.getlocal(zone) + }.should.raise(TypeError, /can't convert \w+ into an exact number/) + end + end + end end end diff --git a/spec/ruby/core/time/getutc_spec.rb b/spec/ruby/core/time/getutc_spec.rb index a6e74cfb98..0cd9c17b00 100644 --- a/spec/ruby/core/time/getutc_spec.rb +++ b/spec/ruby/core/time/getutc_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/getgm', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/getgm' describe "Time#getutc" do - it_behaves_like(:time_getgm, :getutc) + it_behaves_like :time_getgm, :getutc end diff --git a/spec/ruby/core/time/gm_spec.rb b/spec/ruby/core/time/gm_spec.rb index a6f2858216..26dffbcedc 100644 --- a/spec/ruby/core/time/gm_spec.rb +++ b/spec/ruby/core/time/gm_spec.rb @@ -1,10 +1,10 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gm', __FILE__) -require File.expand_path('../shared/time_params', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gm' +require_relative 'shared/time_params' describe "Time.gm" do - it_behaves_like(:time_gm, :gm) - it_behaves_like(:time_params, :gm) - it_behaves_like(:time_params_10_arg, :gm) - it_behaves_like(:time_params_microseconds, :gm) + it_behaves_like :time_gm, :gm + it_behaves_like :time_params, :gm + it_behaves_like :time_params_10_arg, :gm + it_behaves_like :time_params_microseconds, :gm end diff --git a/spec/ruby/core/time/gmt_offset_spec.rb b/spec/ruby/core/time/gmt_offset_spec.rb index b7613eed2f..df417e6e4e 100644 --- a/spec/ruby/core/time/gmt_offset_spec.rb +++ b/spec/ruby/core/time/gmt_offset_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gmt_offset', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gmt_offset' describe "Time#gmt_offset" do - it_behaves_like(:time_gmt_offset, :gmt_offset) + it_behaves_like :time_gmt_offset, :gmt_offset end diff --git a/spec/ruby/core/time/gmt_spec.rb b/spec/ruby/core/time/gmt_spec.rb index 78ebcd0f5e..840f59e0e8 100644 --- a/spec/ruby/core/time/gmt_spec.rb +++ b/spec/ruby/core/time/gmt_spec.rb @@ -1,8 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#gmt?" do it "returns true if time represents a time in UTC (GMT)" do - Time.now.gmt?.should == false - Time.now.gmtime.gmt?.should == true + Time.now.should_not.gmt? + Time.now.gmtime.should.gmt? end end diff --git a/spec/ruby/core/time/gmtime_spec.rb b/spec/ruby/core/time/gmtime_spec.rb index 49a1f10479..d965cd541d 100644 --- a/spec/ruby/core/time/gmtime_spec.rb +++ b/spec/ruby/core/time/gmtime_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gmtime', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gmtime' describe "Time#gmtime" do - it_behaves_like(:time_gmtime, :gmtime) + it_behaves_like :time_gmtime, :gmtime end diff --git a/spec/ruby/core/time/gmtoff_spec.rb b/spec/ruby/core/time/gmtoff_spec.rb index 505b5d0c1b..fa28520e9e 100644 --- a/spec/ruby/core/time/gmtoff_spec.rb +++ b/spec/ruby/core/time/gmtoff_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gmt_offset', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gmt_offset' describe "Time#gmtoff" do - it_behaves_like(:time_gmt_offset, :gmtoff) + it_behaves_like :time_gmt_offset, :gmtoff end diff --git a/spec/ruby/core/time/hash_spec.rb b/spec/ruby/core/time/hash_spec.rb index 77014c5dc8..1cfc56eab0 100644 --- a/spec/ruby/core/time/hash_spec.rb +++ b/spec/ruby/core/time/hash_spec.rb @@ -1,8 +1,8 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#hash" do - it "returns a Fixnum" do - Time.at(100).hash.should be_an_instance_of(Fixnum) + it "returns an Integer" do + Time.at(100).hash.should.instance_of?(Integer) end it "is stable" do diff --git a/spec/ruby/core/time/hour_spec.rb b/spec/ruby/core/time/hour_spec.rb index 65a2ae6ad7..ca69c25adb 100644 --- a/spec/ruby/core/time/hour_spec.rb +++ b/spec/ruby/core/time/hour_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#hour" do it "returns the hour of the day (0..23) for a local Time" do diff --git a/spec/ruby/core/time/inspect_spec.rb b/spec/ruby/core/time/inspect_spec.rb index 7f57a2c4cb..c3a4519a24 100644 --- a/spec/ruby/core/time/inspect_spec.rb +++ b/spec/ruby/core/time/inspect_spec.rb @@ -1,6 +1,33 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/inspect', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/inspect' describe "Time#inspect" do it_behaves_like :inspect, :inspect + + it "preserves microseconds" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 123456) + t.inspect.should == "2007-11-01 15:25:00.123456 UTC" + end + + it "omits trailing zeros from microseconds" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 100000) + t.inspect.should == "2007-11-01 15:25:00.1 UTC" + end + + it "uses the correct time zone without microseconds" do + t = Time.utc(2000, 1, 1) + t = t.localtime(9*3600) + t.inspect.should == "2000-01-01 09:00:00 +0900" + end + + it "uses the correct time zone with microseconds" do + t = Time.utc(2000, 1, 1, 0, 0, 0, 123456) + t = t.localtime(9*3600) + t.inspect.should == "2000-01-01 09:00:00.123456 +0900" + end + + it "preserves nanoseconds" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r) + t.inspect.should == "2007-11-01 15:25:00.123456789 UTC" + end end diff --git a/spec/ruby/core/time/isdst_spec.rb b/spec/ruby/core/time/isdst_spec.rb index de71bf68ff..173230ca07 100644 --- a/spec/ruby/core/time/isdst_spec.rb +++ b/spec/ruby/core/time/isdst_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/isdst', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/isdst' describe "Time#isdst" do - it_behaves_like(:time_isdst, :isdst) + it_behaves_like :time_isdst, :isdst end diff --git a/spec/ruby/core/time/iso8601_spec.rb b/spec/ruby/core/time/iso8601_spec.rb new file mode 100644 index 0000000000..ad60c3bb32 --- /dev/null +++ b/spec/ruby/core/time/iso8601_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#iso8601" do + it_behaves_like :time_xmlschema, :iso8601 +end diff --git a/spec/ruby/core/time/local_spec.rb b/spec/ruby/core/time/local_spec.rb index 63c644e4ea..581ed171d5 100644 --- a/spec/ruby/core/time/local_spec.rb +++ b/spec/ruby/core/time/local_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/local', __FILE__) -require File.expand_path('../shared/time_params', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/local' +require_relative 'shared/time_params' describe "Time.local" do - it_behaves_like(:time_local, :local) - it_behaves_like(:time_local_10_arg, :local) - it_behaves_like(:time_params, :local) - it_behaves_like(:time_params_10_arg, :local) - it_behaves_like(:time_params_microseconds, :local) + it_behaves_like :time_local, :local + it_behaves_like :time_local_10_arg, :local + it_behaves_like :time_params, :local + it_behaves_like :time_params_10_arg, :local + it_behaves_like :time_params_microseconds, :local end diff --git a/spec/ruby/core/time/localtime_spec.rb b/spec/ruby/core/time/localtime_spec.rb index 2fc8a2d56a..1c0b11b7a6 100644 --- a/spec/ruby/core/time/localtime_spec.rb +++ b/spec/ruby/core/time/localtime_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#localtime" do it "converts self to local time, modifying the receiver" do @@ -12,7 +12,7 @@ describe "Time#localtime" do it "returns self" do t = Time.gm(2007, 1, 9, 12, 0, 0) - t.localtime.should equal(t) + t.localtime.should.equal?(t) end it "converts time to the UTC offset specified as an Integer number of seconds" do @@ -26,13 +26,13 @@ describe "Time#localtime" do it "does not raise an error if already in the right time zone" do time = Time.now time.freeze - time.localtime.should equal(time) + time.localtime.should.equal?(time) end - it "raises a RuntimeError if the time has a different time zone" do + it "raises a FrozenError if the time has a different time zone" do time = Time.gm(2007, 1, 9, 12, 0, 0) time.freeze - lambda { time.localtime }.should raise_error(RuntimeError) + -> { time.localtime }.should.raise(FrozenError) end end @@ -51,7 +51,7 @@ describe "Time#localtime" do t = Time.gm(2007, 1, 9, 12, 0, 0) t.localtime(Rational(7201, 2)) t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2)) - t.utc_offset.should eql(Rational(7201, 2)) + t.utc_offset.should.eql?(Rational(7201, 2)) end describe "with an argument that responds to #to_r" do @@ -61,7 +61,7 @@ describe "Time#localtime" do t = Time.gm(2007, 1, 9, 12, 0, 0) t.localtime(o) t.should == Time.new(2007, 1, 9, 13, 0, Rational(1, 2), Rational(7201, 2)) - t.utc_offset.should eql(Rational(7201, 2)) + t.utc_offset.should.eql?(Rational(7201, 2)) end end @@ -72,6 +72,13 @@ describe "Time#localtime" do t.utc_offset.should == 3600 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("+01:00:01") + t.should == Time.new(2007, 1, 9, 13, 0, 1, 3601) + t.utc_offset.should == 3601 + end + it "returns a Time with a UTC offset specified as -HH:MM" do t = Time.gm(2007, 1, 9, 12, 0, 0) t.localtime("-01:00") @@ -79,6 +86,51 @@ describe "Time#localtime" do t.utc_offset.should == -3600 end + it "returns a Time with a UTC offset specified as -HH:MM:SS" do + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime("-01:00:01") + t.should == Time.new(2007, 1, 9, 10, 59, 59, -3601) + t.utc_offset.should == -3601 + end + + it "returns a Time with a UTC offset specified as UTC" do + t = Time.new(2007, 1, 9, 12, 0, 0, 3600) + t.localtime("UTC") + t.utc_offset.should == 0 + end + + it "returns a Time with a UTC offset specified as A-Z military zone" do + t = Time.new(2007, 1, 9, 12, 0, 0, 3600) + t.localtime("B") + t.utc_offset.should == 3600 * 2 + end + + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now.localtime("+24:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+2400") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.now.localtime("+99:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now.localtime("+9900") }.should.raise(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now.localtime("+00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now.localtime("+0060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now.localtime("+00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now.localtime("+0099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now.localtime("+00:00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now.localtime("+000060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now.localtime("+00:00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now.localtime("+000099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + platform_is_not :windows do it "changes the timezone according to the set one" do t = Time.new(2005, 2, 27, 22, 50, 0, -3600) @@ -93,7 +145,7 @@ describe "Time#localtime" do it "does nothing if already in a local time zone" do time = with_timezone("America/New_York") do - break Time.new(2005, 2, 27, 22, 50, 0) + Time.new(2005, 2, 27, 22, 50, 0) end zone = time.zone @@ -116,25 +168,36 @@ describe "Time#localtime" do end end + describe "with an argument that responds to #utc_to_local" do + it "coerces using #utc_to_local" do + o = mock('string') + o.should_receive(:utc_to_local).and_return(Time.new(2007, 1, 9, 13, 0, 0, 3600)) + t = Time.gm(2007, 1, 9, 12, 0, 0) + t.localtime(o) + t.should == Time.new(2007, 1, 9, 13, 0, 0, 3600) + t.utc_offset.should == 3600 + end + end + it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do t = Time.now - lambda { t.localtime("3600") }.should raise_error(ArgumentError) + -> { t.localtime("3600") }.should.raise(ArgumentError) end it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do t = Time.now - lambda { t.localtime("-01:00".encode("UTF-16LE")) }.should raise_error(ArgumentError) + -> { t.localtime("-01:00".encode("UTF-16LE")) }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do t = Time.new t.localtime(-86400 + 1).utc_offset.should == (-86400 + 1) - lambda { t.localtime(-86400) }.should raise_error(ArgumentError) + -> { t.localtime(-86400) }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do t = Time.new t.localtime(86400 - 1).utc_offset.should == (86400 - 1) - lambda { t.localtime(86400) }.should raise_error(ArgumentError) + -> { t.localtime(86400) }.should.raise(ArgumentError) end end diff --git a/spec/ruby/core/time/mday_spec.rb b/spec/ruby/core/time/mday_spec.rb index 5fbff299cc..3c21939890 100644 --- a/spec/ruby/core/time/mday_spec.rb +++ b/spec/ruby/core/time/mday_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/day', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/day' describe "Time#mday" do - it_behaves_like(:time_day, :mday) + it_behaves_like :time_day, :mday end diff --git a/spec/ruby/core/time/min_spec.rb b/spec/ruby/core/time/min_spec.rb index c1c3ebed3b..7d087d4046 100644 --- a/spec/ruby/core/time/min_spec.rb +++ b/spec/ruby/core/time/min_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#min" do it "returns the minute of the hour (0..59) for a local Time" do diff --git a/spec/ruby/core/time/minus_spec.rb b/spec/ruby/core/time/minus_spec.rb index 4e2bb60333..ee3d8acda8 100644 --- a/spec/ruby/core/time/minus_spec.rb +++ b/spec/ruby/core/time/minus_spec.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Time#-" do it "decrements the time by the specified amount" do @@ -19,18 +20,18 @@ describe "Time#-" do end it "raises a TypeError if given argument is a coercible String" do - lambda { Time.now - "1" }.should raise_error(TypeError) - lambda { Time.now - "0.1" }.should raise_error(TypeError) - lambda { Time.now - "1/3" }.should raise_error(TypeError) + -> { Time.now - "1" }.should.raise(TypeError) + -> { Time.now - "0.1" }.should.raise(TypeError) + -> { Time.now - "1/3" }.should.raise(TypeError) end it "raises TypeError on argument that can't be coerced" do - lambda { Time.now - Object.new }.should raise_error(TypeError) - lambda { Time.now - "stuff" }.should raise_error(TypeError) + -> { Time.now - Object.new }.should.raise(TypeError) + -> { Time.now - "stuff" }.should.raise(TypeError) end it "raises TypeError on nil argument" do - lambda { Time.now - nil }.should raise_error(TypeError) + -> { Time.now - nil }.should.raise(TypeError) end it "tracks microseconds" do @@ -78,21 +79,38 @@ describe "Time#-" do end it "returns a UTC time if self is UTC" do - (Time.utc(2012) - 10).utc?.should == true + (Time.utc(2012) - 10).should.utc? end it "returns a non-UTC time if self is non-UTC" do - (Time.local(2012) - 10).utc?.should == false + (Time.local(2012) - 10).should_not.utc? end it "returns a time with the same fixed offset as self" do (Time.new(2012, 1, 1, 0, 0, 0, 3600) - 10).utc_offset.should == 3600 end + it "preserves time zone" do + time_with_zone = Time.now.utc + time_with_zone.zone.should == (time_with_zone - 1).zone + + time_with_zone = Time.now + time_with_zone.zone.should == (time_with_zone - 1).zone + end + + context "zone is a timezone object" do + it "preserves time zone" do + zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) + time = Time.new(2012, 1, 1, 12, 0, 0, zone) - 1 + + time.zone.should == zone + end + end + it "does not return a subclass instance" do c = Class.new(Time) - x = c.now + 1 - x.should be_an_instance_of(Time) + x = c.now - 1 + x.should.instance_of?(Time) end it "returns a time with nanoseconds precision between two time objects" do diff --git a/spec/ruby/core/time/mktime_spec.rb b/spec/ruby/core/time/mktime_spec.rb index 68ac1b90ac..78a6a6e772 100644 --- a/spec/ruby/core/time/mktime_spec.rb +++ b/spec/ruby/core/time/mktime_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/local', __FILE__) -require File.expand_path('../shared/time_params', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/local' +require_relative 'shared/time_params' describe "Time.mktime" do - it_behaves_like(:time_local, :mktime) - it_behaves_like(:time_local_10_arg, :mktime) - it_behaves_like(:time_params, :mktime) - it_behaves_like(:time_params_10_arg, :mktime) - it_behaves_like(:time_params_microseconds, :mktime) + it_behaves_like :time_local, :mktime + it_behaves_like :time_local_10_arg, :mktime + it_behaves_like :time_params, :mktime + it_behaves_like :time_params_10_arg, :mktime + it_behaves_like :time_params_microseconds, :mktime end diff --git a/spec/ruby/core/time/mon_spec.rb b/spec/ruby/core/time/mon_spec.rb index 2408341143..f41b39648b 100644 --- a/spec/ruby/core/time/mon_spec.rb +++ b/spec/ruby/core/time/mon_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/month', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/month' describe "Time#mon" do - it_behaves_like(:time_month, :mon) + it_behaves_like :time_month, :mon end diff --git a/spec/ruby/core/time/monday_spec.rb b/spec/ruby/core/time/monday_spec.rb index 47b09c9a07..47ecaeb1db 100644 --- a/spec/ruby/core/time/monday_spec.rb +++ b/spec/ruby/core/time/monday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#monday?" do it "returns true if time represents Monday" do - Time.local(2000, 1, 3).monday?.should == true + Time.local(2000, 1, 3).should.monday? end it "returns false if time doesn't represent Monday" do - Time.local(2000, 1, 1).monday?.should == false + Time.local(2000, 1, 1).should_not.monday? end end diff --git a/spec/ruby/core/time/month_spec.rb b/spec/ruby/core/time/month_spec.rb index 6323c6205a..81e20384ab 100644 --- a/spec/ruby/core/time/month_spec.rb +++ b/spec/ruby/core/time/month_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/month', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/month' describe "Time#month" do - it_behaves_like(:time_month, :month) + it_behaves_like :time_month, :month end diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index a92715c81c..91ce4b2e3a 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -1,20 +1,21 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/now', __FILE__) -require File.expand_path('../shared/local', __FILE__) -require File.expand_path('../shared/time_params', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/now' +require_relative 'shared/local' +require_relative 'shared/time_params' describe "Time.new" do - it_behaves_like(:time_now, :new) + it_behaves_like :time_now, :new end describe "Time.new" do - it_behaves_like(:time_local, :new) - it_behaves_like(:time_params, :new) + it_behaves_like :time_local, :new + it_behaves_like :time_params, :new end describe "Time.new with a utc_offset argument" do it "returns a non-UTC time" do - Time.new(2000, 1, 1, 0, 0, 0, 0).utc?.should == false + Time.new(2000, 1, 1, 0, 0, 0, 0).should_not.utc? end it "returns a Time with a UTC offset of the specified number of Integer seconds" do @@ -30,14 +31,14 @@ describe "Time.new with a utc_offset argument" do end it "returns a Time with a UTC offset of the specified number of Rational seconds" do - Time.new(2000, 1, 1, 0, 0, 0, Rational(5, 2)).utc_offset.should eql(Rational(5, 2)) + Time.new(2000, 1, 1, 0, 0, 0, Rational(5, 2)).utc_offset.should.eql?(Rational(5, 2)) end describe "with an argument that responds to #to_r" do it "coerces using #to_r" do o = mock_numeric('rational') o.should_receive(:to_r).and_return(Rational(5, 2)) - Time.new(2000, 1, 1, 0, 0, 0, o).utc_offset.should eql(Rational(5, 2)) + Time.new(2000, 1, 1, 0, 0, 0, o).utc_offset.should.eql?(Rational(5, 2)) end end @@ -49,6 +50,38 @@ describe "Time.new with a utc_offset argument" do Time.new(2000, 1, 1, 0, 0, 0, "-04:10").utc_offset.should == -15000 end + it "returns a Time with a UTC offset specified as +HH:MM:SS" do + Time.new(2000, 1, 1, 0, 0, 0, "+05:30:37").utc_offset.should == 19837 + end + + it "returns a Time with a UTC offset specified as -HH:MM" do + Time.new(2000, 1, 1, 0, 0, 0, "-04:10:43").utc_offset.should == -15043 + end + + it "returns a Time with a UTC offset specified as +HH" do + Time.new(2000, 1, 1, 0, 0, 0, "+05").utc_offset.should == 3600 * 5 + end + + it "returns a Time with a UTC offset specified as -HH" do + Time.new(2000, 1, 1, 0, 0, 0, "-05").utc_offset.should == -3600 * 5 + end + + it "returns a Time with a UTC offset specified as +HHMM" do + Time.new(2000, 1, 1, 0, 0, 0, "+0530").utc_offset.should == 19800 + end + + it "returns a Time with a UTC offset specified as -HHMM" do + Time.new(2000, 1, 1, 0, 0, 0, "-0530").utc_offset.should == -19800 + end + + it "returns a Time with a UTC offset specified as +HHMMSS" do + Time.new(2000, 1, 1, 0, 0, 0, "+053037").utc_offset.should == 19837 + end + + it "returns a Time with a UTC offset specified as -HHMMSS" do + Time.new(2000, 1, 1, 0, 0, 0, "-053037").utc_offset.should == -19837 + end + describe "with an argument that responds to #to_str" do it "coerces using #to_str" do o = mock('string') @@ -57,6 +90,48 @@ describe "Time.new with a utc_offset argument" do end end + it "returns a Time with UTC offset specified as UTC" do + Time.new(2000, 1, 1, 0, 0, 0, "UTC").utc_offset.should == 0 + end + + it "returns a Time with UTC offset specified as a single letter military timezone" do + [ + ["A", 3600], + ["B", 3600 * 2], + ["C", 3600 * 3], + ["D", 3600 * 4], + ["E", 3600 * 5], + ["F", 3600 * 6], + ["G", 3600 * 7], + ["H", 3600 * 8], + ["I", 3600 * 9], + # J is not supported + ["K", 3600 * 10], + ["L", 3600 * 11], + ["M", 3600 * 12], + ["N", 3600 * -1], + ["O", 3600 * -2], + ["P", 3600 * -3], + ["Q", 3600 * -4], + ["R", 3600 * -5], + ["S", 3600 * -6], + ["T", 3600 * -7], + ["U", 3600 * -8], + ["V", 3600 * -9], + ["W", 3600 * -10], + ["X", 3600 * -11], + ["Y", 3600 * -12], + ["Z", 0] + ].each do |letter, offset| + Time.new(2000, 1, 1, 0, 0, 0, letter).utc_offset.should == offset + end + end + + it "raises ArgumentError if the string argument is J" do + message = '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: J' + -> { Time.new(2000, 1, 1, 0, 0, 0, "J") }.should.raise(ArgumentError, message) + end + it "returns a local Time if the argument is nil" do with_timezone("PST", -8) do t = Time.new(2000, 1, 1, 0, 0, 0, nil) @@ -67,33 +142,598 @@ describe "Time.new with a utc_offset argument" do # [Bug #8679], r47676 it "disallows a value for minutes greater than 59" do - lambda { + -> { Time.new(2000, 1, 1, 0, 0, 0, "+01:60") - }.should raise_error(ArgumentError) - lambda { + }.should.raise(ArgumentError) + -> { Time.new(2000, 1, 1, 0, 0, 0, "+01:99") - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises ArgumentError if the String argument is not of the form (+|-)HH:MM" do - lambda { Time.new(2000, 1, 1, 0, 0, 0, "3600") }.should raise_error(ArgumentError) + -> { Time.new(2000, 1, 1, 0, 0, 0, "3600") }.should.raise(ArgumentError) end it "raises ArgumentError if the hour value is greater than 23" do - lambda { Time.new(2000, 1, 1, 0, 0, 0, "+24:00") }.should raise_error(ArgumentError) + -> { Time.new(2000, 1, 1, 0, 0, 0, "+24:00") }.should.raise(ArgumentError) end it "raises ArgumentError if the String argument is not in an ASCII-compatible encoding" do - lambda { Time.new(2000, 1, 1, 0, 0, 0, "-04:10".encode("UTF-16LE")) }.should raise_error(ArgumentError) + # Don't check exception message - it was changed in previous CRuby versions: + # - "string contains null byte" + # - '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset' + -> { + Time.new(2000, 1, 1, 0, 0, 0, "-04:10".encode("UTF-16LE")) + }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value less than or equal to -86400 seconds" do Time.new(2000, 1, 1, 0, 0, 0, -86400 + 1).utc_offset.should == (-86400 + 1) - lambda { Time.new(2000, 1, 1, 0, 0, 0, -86400) }.should raise_error(ArgumentError) + -> { Time.new(2000, 1, 1, 0, 0, 0, -86400) }.should.raise(ArgumentError) end it "raises ArgumentError if the argument represents a value greater than or equal to 86400 seconds" do Time.new(2000, 1, 1, 0, 0, 0, 86400 - 1).utc_offset.should == (86400 - 1) - lambda { Time.new(2000, 1, 1, 0, 0, 0, 86400) }.should raise_error(ArgumentError) + -> { Time.new(2000, 1, 1, 0, 0, 0, 86400) }.should.raise(ArgumentError) + end + + it "raises ArgumentError if the utc_offset argument is greater than or equal to 10e9" do + -> { Time.new(2000, 1, 1, 0, 0, 0, 1000000000) }.should.raise(ArgumentError) + end +end + +# The method #local_to_utc is tested only here because Time.new is the only method that calls #local_to_utc. +describe "Time.new with a timezone argument" do + it "returns a Time in the timezone" do + zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) + time = Time.new(2000, 1, 1, 12, 0, 0, zone) + + time.zone.should == zone + time.utc_offset.should == 5*3600+30*60 + time.wday.should == 6 + time.yday.should == 1 + end + + it "accepts timezone argument that must have #local_to_utc and #utc_to_local methods" do + zone = Object.new + def zone.utc_to_local(time) + time + end + def zone.local_to_utc(time) + time + end + + Time.new(2000, 1, 1, 12, 0, 0, zone).should.is_a?(Time) + end + + it "raises TypeError if timezone does not implement #local_to_utc method" do + zone = Object.new + def zone.utc_to_local(time) + time + end + + -> { + Time.new(2000, 1, 1, 12, 0, 0, zone) + }.should.raise(TypeError, /can't convert Object into an exact number/) + end + + it "does not raise exception if timezone does not implement #utc_to_local method" do + zone = Object.new + def zone.local_to_utc(time) + time + end + + Time.new(2000, 1, 1, 12, 0, 0, zone).should.is_a?(Time) + end + + # The result also should be a Time or Time-like object (not necessary to be the same class) + # or respond to #to_int method. The zone of the result is just ignored. + describe "returned value by #utc_to_local and #local_to_utc methods" do + it "could be Time instance" do + zone = Object.new + def zone.local_to_utc(t) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec) + time - 60 * 60 # - 1 hour + end + + Time.new(2000, 1, 1, 12, 0, 0, zone).should.is_a?(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 + end + + it "could be Time subclass instance" do + zone = Object.new + def zone.local_to_utc(t) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec) + time -= 60 * 60 # - 1 hour + Class.new(Time).utc(time.year, time.mon, time.day, time.hour, t.min, t.sec) + end + + Time.new(2000, 1, 1, 12, 0, 0, zone).should.is_a?(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 + end + + it "could be any object with #to_i method" do + zone = Object.new + def zone.local_to_utc(time) + obj = Object.new + obj.singleton_class.define_method(:to_i) { time.to_i - 60*60 } + obj + end + + Time.new(2000, 1, 1, 12, 0, 0, zone).should.is_a?(Time) + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 60*60 + end + + it "could have any #zone and #utc_offset because they are ignored if it isn't an instance of Time" do + zone = Object.new + def zone.local_to_utc(time) + Struct.new(:to_i, :zone, :utc_offset).new(time.to_i, 'America/New_York', -5*60*60) + end + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 0 + + zone = Object.new + def zone.local_to_utc(time) + Struct.new(:to_i, :zone, :utc_offset).new(time.to_i, 'Asia/Tokyo', 9*60*60) + end + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 0 + end + + it "cannot have arbitrary #utc_offset if it is an instance of Time" do + zone = Object.new + def zone.local_to_utc(t) + Time.new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, 9*60*60) + end + Time.new(2000, 1, 1, 12, 0, 0, zone).utc_offset.should == 9*60*60 + end + + it "raises ArgumentError if difference between argument and result is too large" do + zone = Object.new + def zone.local_to_utc(t) + Time.utc(t.year, t.mon, t.day + 1, t.hour, t.min, t.sec) + end + + -> { + Time.new(2000, 1, 1, 12, 0, 0, zone) + }.should.raise(ArgumentError, "utc_offset out of range") + end + end + + # https://github.com/ruby/ruby/blob/v2_6_0/time.c#L5330 + # + # Time-like argument to these methods is similar to a Time object in UTC without sub-second; + # it has attribute readers for the parts, e.g. year, month, and so on, and epoch time readers, to_i + # + # The sub-second attributes are fixed as 0, and utc_offset, zone, isdst, and their aliases are same as a Time object in UTC + describe "Time-like argument of #utc_to_local and #local_to_utc methods" do + before do + @obj = TimeSpecs::TimeLikeArgumentRecorder.result + @obj.should_not == nil + end + + it "implements subset of Time methods" do + # List only methods that are explicitly documented. + [ + :year, :mon, :mday, :hour, :min, :sec, :to_i, :isdst + ].each do |name| + @obj.respond_to?(name).should == true + end + end + + it "has attribute values the same as a Time object in UTC" do + @obj.usec.should == 0 + @obj.nsec.should == 0 + @obj.subsec.should == 0 + @obj.tv_usec.should == 0 + @obj.tv_nsec.should == 0 + + @obj.utc_offset.should == 0 + @obj.zone.should == "UTC" + @obj.isdst.should == Time.new.utc.isdst + end + end + + context "#name method" do + it "uses the optional #name method for marshaling" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.new(2000, 1, 1, 12, 0, 0, zone) + time_loaded = Marshal.load(Marshal.dump(time)) + + time_loaded.zone.should == "Asia/Colombo" + time_loaded.utc_offset.should == 5*3600+30*60 + end + + it "cannot marshal Time if #name method isn't implemented" do + zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) + time = Time.new(2000, 1, 1, 12, 0, 0, zone) + + -> { + Marshal.dump(time) + }.should.raise(NoMethodError, /undefined method [`']name' for/) + end + end + + it "the #abbr method is used by '%Z' in #strftime" do + zone = TimeSpecs::TimezoneWithAbbr.new(name: "Asia/Colombo") + time = Time.new(2000, 1, 1, 12, 0, 0, zone) + + time.strftime("%Z").should == "MMT" + end + + # At loading marshaled data, a timezone name will be converted to a timezone object + # by find_timezone class method, if the method is defined. + # Similarly, that class method will be called when a timezone argument does not have + # the necessary methods mentioned above. + context "subject's class implements .find_timezone method" do + it "calls .find_timezone to build a time object at loading marshaled data" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, zone) + time_loaded = Marshal.load(Marshal.dump(time)) + + time_loaded.zone.should.is_a? TimeSpecs::TimezoneWithName + time_loaded.zone.name.should == "Asia/Colombo" + time_loaded.utc_offset.should == 5*3600+30*60 + end + + it "calls .find_timezone to build a time object if passed zone name as a timezone argument" do + time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, "Asia/Colombo") + time.zone.should.is_a? TimeSpecs::TimezoneWithName + time.zone.name.should == "Asia/Colombo" + + time = TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, "some invalid zone name") + time.zone.should.is_a? TimeSpecs::TimezoneWithName + time.zone.name.should == "some invalid zone name" + end + + it "does not call .find_timezone if passed any not string/numeric/timezone timezone argument" do + [Object.new, [], {}, :"some zone"].each do |zone| + -> { + TimeSpecs::TimeWithFindTimezone.new(2000, 1, 1, 12, 0, 0, zone) + }.should.raise(TypeError, /can't convert \w+ into an exact number/) + end + end + end + + describe ":in keyword argument" do + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.new(2000, 1, 1, 12, 0, 0, in: "+05:00") + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + + time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00") + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + + time = Time.new(2000, 1, 1, 12, 0, 0, in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + end + + it "could be UTC offset as a number of seconds" do + time = Time.new(2000, 1, 1, 12, 0, 0, in: 5*60*60) + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + + time = Time.new(2000, 1, 1, 12, 0, 0, in: -9*60*60) + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end + + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.new(2000, 1, 1, 0, 0, 0, in: "W").utc_offset.should == 3600 * -10 + end + + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) + + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone + + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.new(2000, 1, 1, 12, 0, 0, in: zone) + + time.utc_offset.should == -9*60*60 + time.zone.should == zone + end + + it "allows omitting minor arguments" do + Time.new(2000, 1, 1, 12, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 1, "+05:00") + Time.new(2000, 1, 1, 12, 1, in: "+05:00").should == Time.new(2000, 1, 1, 12, 1, 0, "+05:00") + Time.new(2000, 1, 1, 12, in: "+05:00").should == Time.new(2000, 1, 1, 12, 0, 0, "+05:00") + Time.new(2000, 1, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, 1, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(2000, in: "+05:00").should == Time.new(2000, 1, 1, 0, 0, 0, "+05:00") + Time.new(in: "+05:00").should be_close(Time.now.getlocal("+05:00"), TIME_TOLERANCE) + end + + it "converts to a provided timezone if all the positional arguments are omitted" do + Time.new(in: "+05:00").utc_offset.should == 5*3600 + end + + it "raises ArgumentError if format is invalid" do + -> { Time.new(2000, 1, 1, 12, 0, 0, in: "+09:99") }.should.raise(ArgumentError) + -> { Time.new(2000, 1, 1, 12, 0, 0, in: "ABC") }.should.raise(ArgumentError) + end + + it "raises ArgumentError if two offset arguments are given" do + -> { + Time.new(2000, 1, 1, 12, 0, 0, "+05:00", in: "+05:00") + }.should.raise(ArgumentError, "timezone argument given as positional and keyword arguments") + end + end + + describe "Time.new with a String argument" do + it "parses an ISO-8601 like format" do + t = Time.utc(2020, 12, 24, 15, 56, 17) + + Time.new("2020-12-24T15:56:17Z").should == t + Time.new("2020-12-25 00:56:17 +09:00").should == t + Time.new("2020-12-25 00:57:47 +09:01:30").should == t + Time.new("2020-12-25 00:56:17 +0900").should == t + Time.new("2020-12-25 00:57:47 +090130").should == t + Time.new("2020-12-25T00:56:17+09:00").should == t + + Time.new("2020-12-25T00:56:17.123456+09:00").should == Time.utc(2020, 12, 24, 15, 56, 17, 123456) + end + + it "accepts precision keyword argument and truncates specified digits of sub-second part" do + Time.new("2021-12-25 00:00:00.123456789876 +09:00").subsec.should == 0.123456789r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: nil).subsec.should == 0.123456789876r + Time.new("2021-12-25 00:00:00 +09:00", precision: 0).subsec.should == 0 + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: -1).subsec.should == 0.123456789876r + end + + it "returns Time in local timezone if not provided in the String argument" do + Time.new("2021-12-25 00:00:00").zone.should == Time.new(2021, 12, 25).zone + Time.new("2021-12-25 00:00:00").utc_offset.should == Time.new(2021, 12, 25).utc_offset + end + + it "returns Time in timezone specified in the String argument" do + Time.new("2021-12-25 00:00:00 +05:00").to_s.should == "2021-12-25 00:00:00 +0500" + end + + it "returns Time in timezone specified in the String argument even if the in keyword argument provided" do + Time.new("2021-12-25 00:00:00 +09:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 +0900" + end + + it "returns Time in timezone specified with in keyword argument if timezone isn't provided in the String argument" do + Time.new("2021-12-25 00:00:00", in: "-01:00").to_s.should == "2021-12-25 00:00:00 -0100" + end + + it "returns Time of Jan 1 for string with just year" do + Time.new("2021").should == Time.new(2021, 1, 1) + Time.new("2021").zone.should == Time.new(2021, 1, 1).zone + Time.new("2021").utc_offset.should == Time.new(2021, 1, 1).utc_offset + end + + it "returns Time of Jan 1 for string with just year in timezone specified with in keyword argument" do + Time.new("2021", in: "+17:00").to_s.should == "2021-01-01 00:00:00 +1700" + end + + it "converts precision keyword argument into Integer if is not nil" do + obj = Object.new + def obj.to_int; 3; end + + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 1.2).subsec.should == 0.1r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: obj).subsec.should == 0.123r + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: 3r).subsec.should == 0.123r + end + + it "returns Time with correct subseconds when given seconds fraction is shorted than 6 digits" do + Time.new("2020-12-25T00:56:17.123 +09:00").nsec.should == 123000000 + Time.new("2020-12-25T00:56:17.123 +09:00").usec.should == 123000 + Time.new("2020-12-25T00:56:17.123 +09:00").subsec.should == 0.123 + end + + it "returns Time with correct subseconds when given seconds fraction is milliseconds" do + Time.new("2020-12-25T00:56:17.123456 +09:00").nsec.should == 123456000 + Time.new("2020-12-25T00:56:17.123456 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456 +09:00").subsec.should == 0.123456 + end + + it "returns Time with correct subseconds when given seconds fraction is longer that 6 digits but shorted than 9 digits" do + Time.new("2020-12-25T00:56:17.12345678 +09:00").nsec.should == 123456780 + Time.new("2020-12-25T00:56:17.12345678 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.12345678 +09:00").subsec.should == 0.12345678 + end + + it "returns Time with correct subseconds when given seconds fraction is nanoseconds" do + Time.new("2020-12-25T00:56:17.123456789 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789 +09:00").subsec.should == 0.123456789 + end + + it "returns Time with correct subseconds when given seconds fraction is longer than 9 digits" do + Time.new("2020-12-25T00:56:17.123456789876 +09:00").nsec.should == 123456789 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").usec.should == 123456 + Time.new("2020-12-25T00:56:17.123456789876 +09:00").subsec.should == 0.123456789 + end + + it "raise TypeError is can't convert precision keyword argument into Integer" do + -> { + Time.new("2021-12-25 00:00:00.123456789876 +09:00", precision: "") + }.should.raise(TypeError, "no implicit conversion of String into Integer") + end + + it "raises ArgumentError if part of time string is missing" do + -> { + Time.new("2020-12-25 00:56 +09:00") + }.should.raise(ArgumentError, /missing sec part: 00:56 |can't parse:/) + + -> { + Time.new("2020-12-25 00 +09:00") + }.should.raise(ArgumentError, /missing min part: 00 |can't parse:/) + end + + it "raises ArgumentError if the time part is missing" do + -> { + Time.new("2020-12-25") + }.should.raise(ArgumentError, /no time information|can't parse:/) + end + + it "raises ArgumentError if day is missing" do + -> { + Time.new("2020-12") + }.should.raise(ArgumentError, /no time information|can't parse:/) + end + + it "raises ArgumentError if subsecond is missing after dot" do + -> { + Time.new("2020-12-25 00:56:17. +0900") + }.should.raise(ArgumentError, /subsecond expected after dot: 00:56:17. |can't parse:/) + end + + it "raises ArgumentError if String argument is not in the supported format" do + -> { + Time.new("021-12-25 00:00:00.123456 +09:00") + }.should.raise(ArgumentError, /year must be 4 or more digits: 021|can't parse:/) + + -> { + Time.new("2020-012-25 00:56:17 +0900") + }.should.raise(ArgumentError, /\Atwo digits mon is expected after [`']-': -012-25 00:\z|can't parse:/) + + -> { + Time.new("2020-2-25 00:56:17 +0900") + }.should.raise(ArgumentError, /\Atwo digits mon is expected after [`']-': -2-25 00:56\z|can't parse:/) + + -> { + Time.new("2020-12-215 00:56:17 +0900") + }.should.raise(ArgumentError, /\Atwo digits mday is expected after [`']-': -215 00:56:\z|can't parse:/) + + -> { + Time.new("2020-12-25 000:56:17 +0900") + }.should.raise(ArgumentError, /two digits hour is expected: 000:56:17 |can't parse:/) + + -> { + Time.new("2020-12-25 0:56:17 +0900") + }.should.raise(ArgumentError, /two digits hour is expected: 0:56:17 \+0|can't parse:/) + + -> { + Time.new("2020-12-25 00:516:17 +0900") + }.should.raise(ArgumentError, /\Atwo digits min is expected after [`']:': :516:17 \+09\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:6:17 +0900") + }.should.raise(ArgumentError, /\Atwo digits min is expected after [`']:': :6:17 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56:137 +0900") + }.should.raise(ArgumentError, /\Atwo digits sec is expected after [`']:': :137 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56:7 +0900") + }.should.raise(ArgumentError, /\Atwo digits sec is expected after [`']:': :7 \+0900\z|can't parse:/) + + -> { + Time.new("2020-12-25 00:56. +0900") + }.should.raise(ArgumentError, /fraction min is not supported: 00:56\.|can't parse:/) + + -> { + Time.new("2020-12-25 00. +0900") + }.should.raise(ArgumentError, /fraction hour is not supported: 00\.|can't parse:/) + end + + it "raises ArgumentError if date/time parts values are not valid" do + -> { + Time.new("2020-13-25 00:56:17 +09:00") + }.should.raise(ArgumentError, /(mon|argument) out of range/) + + -> { + Time.new("2020-12-32 00:56:17 +09:00") + }.should.raise(ArgumentError, /(mday|argument) out of range/) + + -> { + Time.new("2020-12-25 25:56:17 +09:00") + }.should.raise(ArgumentError, /(hour|argument) out of range/) + + -> { + Time.new("2020-12-25 00:61:17 +09:00") + }.should.raise(ArgumentError, /(min|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:61 +09:00") + }.should.raise(ArgumentError, /(sec|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +23:59:60") + }.should.raise(ArgumentError, /utc_offset|argument out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +24:00") + }.should.raise(ArgumentError, /(utc_offset|argument) out of range/) + + -> { + Time.new("2020-12-25 00:56:17 +23:61") + }.should.raise(ArgumentError, /utc_offset/) + + ruby_bug '#20797', ''...'3.4' do + -> { + Time.new("2020-12-25 00:56:17 +00:23:61") + }.should.raise(ArgumentError, /utc_offset/) + end + end + + it "raises ArgumentError if utc offset parts are not valid" do + -> { Time.new("2020-12-25 00:56:17 +24:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +2400") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.new("2020-12-25 00:56:17 +99:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.new("2020-12-25 00:56:17 +9900") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.new("2020-12-25 00:56:17 +00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.new("2020-12-25 00:56:17 +0060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.new("2020-12-25 00:56:17 +00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.new("2020-12-25 00:56:17 +0099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + + ruby_bug '#20797', ''...'3.4' do + -> { Time.new("2020-12-25 00:56:17 +00:00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.new("2020-12-25 00:56:17 +000060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.new("2020-12-25 00:56:17 +00:00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.new("2020-12-25 00:56:17 +000099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + + it "raises ArgumentError if string has not ascii-compatible encoding" do + -> { + Time.new("2021-11-31 00:00:60 +09:00".encode("utf-32le")) + }.should.raise(ArgumentError, "time string should have ASCII compatible encoding") + end + + it "raises ArgumentError if string doesn't start with year" do + -> { + Time.new("a\nb") + }.should.raise(ArgumentError, "can't parse: \"a\\nb\"") + end + + it "raises ArgumentError if string has extra characters after offset" do + -> { + Time.new("2021-11-31 00:00:59 +09:00 abc") + }.should.raise(ArgumentError, /can't parse.+ abc/) + end + + it "raises ArgumentError when there are leading space characters" do + -> { Time.new(" 2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("\t2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("\n2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("\v2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("\f2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("\r2020-12-02 00:00:00") }.should.raise(ArgumentError, /can't parse/) + end + + it "raises ArgumentError when there are trailing whitespaces" do + -> { Time.new("2020-12-02 00:00:00 ") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\t") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\n") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\v") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\f") }.should.raise(ArgumentError, /can't parse/) + -> { Time.new("2020-12-02 00:00:00\r") }.should.raise(ArgumentError, /can't parse/) + end end end diff --git a/spec/ruby/core/time/now_spec.rb b/spec/ruby/core/time/now_spec.rb index 399a1a22e2..533cf68380 100644 --- a/spec/ruby/core/time/now_spec.rb +++ b/spec/ruby/core/time/now_spec.rb @@ -1,6 +1,181 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/now', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/now' describe "Time.now" do - it_behaves_like(:time_now, :now) + it_behaves_like :time_now, :now + + describe ":in keyword argument" do + it "could be UTC offset as a String in '+HH:MM or '-HH:MM' format" do + time = Time.now(in: "+05:00") + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + + time = Time.now(in: "-09:00") + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + + time = Time.now(in: "-09:00:01") + + time.utc_offset.should == -(9*60*60 + 1) + time.zone.should == nil + end + + it "could be UTC offset as a number of seconds" do + time = Time.now(in: 5*60*60) + + time.utc_offset.should == 5*60*60 + time.zone.should == nil + + time = Time.now(in: -9*60*60) + + time.utc_offset.should == -9*60*60 + time.zone.should == nil + end + + it "returns a Time with UTC offset specified as a single letter military timezone" do + Time.now(in: "W").utc_offset.should == 3600 * -10 + end + + it "could be a timezone object" do + zone = TimeSpecs::TimezoneWithName.new(name: "Asia/Colombo") + time = Time.now(in: zone) + + time.utc_offset.should == 5*3600+30*60 + time.zone.should == zone + + zone = TimeSpecs::TimezoneWithName.new(name: "PST") + time = Time.now(in: zone) + + time.utc_offset.should == -9*60*60 + time.zone.should == zone + end + + it "raises ArgumentError if format is invalid" do + -> { Time.now(in: "+09:99") }.should.raise(ArgumentError) + -> { Time.now(in: "ABC") }.should.raise(ArgumentError) + end + + it "raises ArgumentError if String argument and hours greater than 23" do + -> { Time.now(in: "+24:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+2400") }.should.raise(ArgumentError, "utc_offset out of range") + + -> { Time.now(in: "+99:00") }.should.raise(ArgumentError, "utc_offset out of range") + -> { Time.now(in: "+9900") }.should.raise(ArgumentError, "utc_offset out of range") + end + + it "raises ArgumentError if String argument and minutes greater than 59" do + -> { Time.now(in: "+00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:60') + -> { Time.now(in: "+0060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0060') + + -> { Time.now(in: "+00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:99') + -> { Time.now(in: "+0099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +0099') + end + + ruby_bug '#20797', ''...'3.4' do + it "raises ArgumentError if String argument and seconds greater than 59" do + -> { Time.now(in: "+00:00:60") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:60') + -> { Time.now(in: "+000060") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000060') + + -> { Time.now(in: "+00:00:99") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +00:00:99') + -> { Time.now(in: "+000099") }.should.raise(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +000099') + end + end + end + + describe "Timezone object" do # https://bugs.ruby-lang.org/issues/17485 + it "raises TypeError if timezone does not implement #utc_to_local method" do + zone = Object.new + def zone.local_to_utc(time) + time + end + + -> { + Time.now(in: zone) + }.should.raise(TypeError, /can't convert Object into an exact number/) + end + + it "does not raise exception if timezone does not implement #local_to_utc method" do + zone = Object.new + def zone.utc_to_local(time) + time + end + + Time.now(in: zone).should.is_a?(Time) + end + + # The result also should be a Time or Time-like object (not necessary to be the same class) + # or Integer. The zone of the result is just ignored. + describe "returned value by #utc_to_local and #local_to_utc methods" do + it "could be Time instance" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.new(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time + 60 * 60 # + 1 hour + end + + Time.now(in: zone).should.is_a?(Time) + Time.now(in: zone).utc_offset.should == 3600 + end + + it "could be Time subclass instance" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.new(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time += 60 * 60 # + 1 hour + + Class.new(Time).new(time.year, time.mon, time.day, time.hour, time.min, time.sec, time.utc_offset) + end + + Time.now(in: zone).should.is_a?(Time) + Time.now(in: zone).utc_offset.should == 3600 + end + + it "could be Integer" do + zone = Object.new + def zone.utc_to_local(time) + time.to_i + 60*60 + end + + Time.now(in: zone).should.is_a?(Time) + Time.now(in: zone).utc_offset.should == 60*60 + end + + it "could have any #zone and #utc_offset because they are ignored" do + zone = Object.new + def zone.utc_to_local(t) + Struct.new(:year, :mon, :mday, :hour, :min, :sec, :isdst, :to_i, :zone, :utc_offset) # rubocop:disable Lint/StructNewOverride + .new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.isdst, t.to_i, 'America/New_York', -5*60*60) + end + Time.now(in: zone).utc_offset.should == 0 + + zone = Object.new + def zone.utc_to_local(t) + Struct.new(:year, :mon, :mday, :hour, :min, :sec, :isdst, :to_i, :zone, :utc_offset) # rubocop:disable Lint/StructNewOverride + .new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.isdst, t.to_i, 'Asia/Tokyo', 9*60*60) + end + Time.now(in: zone).utc_offset.should == 0 + + zone = Object.new + def zone.utc_to_local(t) + Time.new(t.year, t.mon, t.mday, t.hour, t.min, t.sec, 9*60*60) + end + Time.now(in: zone).utc_offset.should == 0 + end + + it "raises ArgumentError if difference between argument and result is too large" do + zone = Object.new + def zone.utc_to_local(t) + time = Time.utc(t.year, t.mon, t.day, t.hour, t.min, t.sec, t.utc_offset) + time -= 24 * 60 * 60 # - 1 day + Time.utc(time.year, time.mon, time.day, time.hour, time.min, time.sec, time.utc_offset) + end + + -> { + Time.now(in: zone) + }.should.raise(ArgumentError, "utc_offset out of range") + end + end + end end diff --git a/spec/ruby/core/time/nsec_spec.rb b/spec/ruby/core/time/nsec_spec.rb index 3a6be1d016..9338eb435a 100644 --- a/spec/ruby/core/time/nsec_spec.rb +++ b/spec/ruby/core/time/nsec_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#nsec" do it "returns 0 for a Time constructed with a whole number of seconds" do @@ -24,4 +24,8 @@ describe "Time#nsec" do it "returns the nanoseconds part of a Time constructed with an Rational number of microseconds" do Time.at(0, Rational(99, 10)).nsec.should == 9900 end + + it "returns a positive value for dates before the epoch" do + Time.utc(1969, 11, 12, 13, 18, 57, 404240).nsec.should == 404240000 + end end diff --git a/spec/ruby/core/time/plus_spec.rb b/spec/ruby/core/time/plus_spec.rb index 29931f8a87..6bd01bcdf3 100644 --- a/spec/ruby/core/time/plus_spec.rb +++ b/spec/ruby/core/time/plus_spec.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Time#+" do it "increments the time by the specified amount" do @@ -16,9 +17,9 @@ describe "Time#+" do end it "raises a TypeError if given argument is a coercible String" do - lambda { Time.now + "1" }.should raise_error(TypeError) - lambda { Time.now + "0.1" }.should raise_error(TypeError) - lambda { Time.now + "1/3" }.should raise_error(TypeError) + -> { Time.now + "1" }.should.raise(TypeError) + -> { Time.now + "0.1" }.should.raise(TypeError) + -> { Time.now + "1/3" }.should.raise(TypeError) end it "increments the time by the specified amount as rational numbers" do @@ -31,34 +32,51 @@ describe "Time#+" do end it "raises TypeError on argument that can't be coerced into Rational" do - lambda { Time.now + Object.new }.should raise_error(TypeError) - lambda { Time.now + "stuff" }.should raise_error(TypeError) + -> { Time.now + Object.new }.should.raise(TypeError) + -> { Time.now + "stuff" }.should.raise(TypeError) end it "returns a UTC time if self is UTC" do - (Time.utc(2012) + 10).utc?.should == true + (Time.utc(2012) + 10).should.utc? end it "returns a non-UTC time if self is non-UTC" do - (Time.local(2012) + 10).utc?.should == false + (Time.local(2012) + 10).should_not.utc? end it "returns a time with the same fixed offset as self" do (Time.new(2012, 1, 1, 0, 0, 0, 3600) + 10).utc_offset.should == 3600 end + it "preserves time zone" do + time_with_zone = Time.now.utc + time_with_zone.zone.should == (time_with_zone + 1).zone + + time_with_zone = Time.now + time_with_zone.zone.should == (time_with_zone + 1).zone + end + + context "zone is a timezone object" do + it "preserves time zone" do + zone = TimeSpecs::Timezone.new(offset: (5*3600+30*60)) + time = Time.new(2012, 1, 1, 12, 0, 0, zone) + 1 + + time.zone.should == zone + end + end + it "does not return a subclass instance" do c = Class.new(Time) x = c.now + 1 - x.should be_an_instance_of(Time) + x.should.instance_of?(Time) end it "raises TypeError on Time argument" do - lambda { Time.now + Time.now }.should raise_error(TypeError) + -> { Time.now + Time.now }.should.raise(TypeError) end it "raises TypeError on nil argument" do - lambda { Time.now + nil }.should raise_error(TypeError) + -> { Time.now + nil }.should.raise(TypeError) end #see [ruby-dev:38446] diff --git a/spec/ruby/core/time/round_spec.rb b/spec/ruby/core/time/round_spec.rb index 97568802b9..a739cabfdf 100644 --- a/spec/ruby/core/time/round_spec.rb +++ b/spec/ruby/core/time/round_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#round" do before do @@ -20,8 +20,8 @@ describe "Time#round" do it "returns an instance of Time, even if #round is called on a subclass" do subclass = Class.new(Time) instance = subclass.at(0) - instance.class.should equal subclass - instance.round.should be_an_instance_of(Time) + instance.class.should.equal? subclass + instance.round.should.instance_of?(Time) end it "copies own timezone to the returning value" do diff --git a/spec/ruby/core/time/saturday_spec.rb b/spec/ruby/core/time/saturday_spec.rb index 0d827a6184..0e51407366 100644 --- a/spec/ruby/core/time/saturday_spec.rb +++ b/spec/ruby/core/time/saturday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#saturday?" do it "returns true if time represents Saturday" do - Time.local(2000, 1, 1).saturday?.should == true + Time.local(2000, 1, 1).should.saturday? end it "returns false if time doesn't represent Saturday" do - Time.local(2000, 1, 2).saturday?.should == false + Time.local(2000, 1, 2).should_not.saturday? end end diff --git a/spec/ruby/core/time/sec_spec.rb b/spec/ruby/core/time/sec_spec.rb index e753235b53..73fc5ce1fc 100644 --- a/spec/ruby/core/time/sec_spec.rb +++ b/spec/ruby/core/time/sec_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#sec" do it "returns the second of the minute(0..60) for time" do diff --git a/spec/ruby/core/time/shared/gm.rb b/spec/ruby/core/time/shared/gm.rb index 72012caa66..0ee602c837 100644 --- a/spec/ruby/core/time/shared/gm.rb +++ b/spec/ruby/core/time/shared/gm.rb @@ -26,4 +26,45 @@ describe :time_gm, shared: true do time.usec.should == 0 time.nsec.should == 999 end + + guard -> { + with_timezone 'right/UTC' do + (Time.gm(1972, 6, 30, 23, 59, 59) + 1).sec == 60 + end + } do + it "handles real leap seconds in zone 'right/UTC'" do + with_timezone 'right/UTC' do + time = Time.send(@method, 1972, 6, 30, 23, 59, 60) + + time.sec.should == 60 + time.min.should == 59 + time.hour.should == 23 + time.day.should == 30 + time.month.should == 6 + end + end + end + + it "handles bad leap seconds by carrying values forward" do + with_timezone 'UTC' do + time = Time.send(@method, 2017, 7, 5, 23, 59, 60) + time.sec.should == 0 + time.min.should == 0 + time.hour.should == 0 + time.day.should == 6 + time.month.should == 7 + end + end + + it "handles a value of 60 for seconds by carrying values forward in zone 'UTC'" do + with_timezone 'UTC' do + time = Time.send(@method, 1972, 6, 30, 23, 59, 60) + + time.sec.should == 0 + time.min.should == 0 + time.hour.should == 0 + time.day.should == 1 + time.month.should == 7 + end + end end diff --git a/spec/ruby/core/time/shared/gmt_offset.rb b/spec/ruby/core/time/shared/gmt_offset.rb index cb842be2f3..839566c249 100644 --- a/spec/ruby/core/time/shared/gmt_offset.rb +++ b/spec/ruby/core/time/shared/gmt_offset.rb @@ -5,6 +5,12 @@ describe :time_gmt_offset, shared: true do end end + it "returns 0 when the date is UTC" do + with_timezone("AST", 3) do + Time.new.utc.send(@method).should == 0 + end + end + platform_is_not :windows do it "returns the correct offset for US Eastern time zone around daylight savings time change" do # "2010-03-14 01:59:59 -0500" + 1 ==> "2010-03-14 03:00:00 -0400" diff --git a/spec/ruby/core/time/shared/gmtime.rb b/spec/ruby/core/time/shared/gmtime.rb index e684a1fd95..aa76b436cc 100644 --- a/spec/ruby/core/time/shared/gmtime.rb +++ b/spec/ruby/core/time/shared/gmtime.rb @@ -4,14 +4,21 @@ describe :time_gmtime, shared: true do with_timezone("CST", -6) do t = Time.local(2007, 1, 9, 6, 0, 0) t.send(@method) - t.should == Time.gm(2007, 1, 9, 12, 0, 0) + # Time#== compensates for time zones, so check all parts separately + t.year.should == 2007 + t.month.should == 1 + t.mday.should == 9 + t.hour.should == 12 + t.min.should == 0 + t.sec.should == 0 + t.zone.should == "UTC" end end it "returns self" do with_timezone("CST", -6) do t = Time.local(2007, 1, 9, 12, 0, 0) - t.send(@method).should equal(t) + t.send(@method).should.equal?(t) end end @@ -19,14 +26,14 @@ describe :time_gmtime, shared: true do it "does not raise an error if already in UTC" do time = Time.gm(2007, 1, 9, 12, 0, 0) time.freeze - time.send(@method).should equal(time) + time.send(@method).should.equal?(time) end - it "raises a RuntimeError if the time is not UTC" do + it "raises a FrozenError if the time is not UTC" do with_timezone("CST", -6) do time = Time.now time.freeze - lambda { time.send(@method) }.should raise_error(RuntimeError) + -> { time.send(@method) }.should.raise(FrozenError) end end end diff --git a/spec/ruby/core/time/shared/inspect.rb b/spec/ruby/core/time/shared/inspect.rb index c707382a6e..82f7f3c686 100644 --- a/spec/ruby/core/time/shared/inspect.rb +++ b/spec/ruby/core/time/shared/inspect.rb @@ -15,9 +15,7 @@ describe :inspect, shared: true do Time.new(2000, 1, 1, 20, 15, 01, 3600).send(@method).should == "2000-01-01 20:15:01 +0100" end - with_feature :encoding do - it "returns a US-ASCII encoded string" do - Time.now.send(@method).encoding.should equal(Encoding::US_ASCII) - end + it "returns a US-ASCII encoded string" do + Time.now.send(@method).encoding.should.equal?(Encoding::US_ASCII) end end diff --git a/spec/ruby/core/time/shared/local.rb b/spec/ruby/core/time/shared/local.rb index 43f331c4c1..068e314999 100644 --- a/spec/ruby/core/time/shared/local.rb +++ b/spec/ruby/core/time/shared/local.rb @@ -7,12 +7,10 @@ describe :time_local, shared: true do end platform_is_not :windows do - describe "timezone changes" do - it "correctly adjusts the timezone change to 'CEST' on 'Europe/Amsterdam'" do - with_timezone("Europe/Amsterdam") do - Time.send(@method, 1940, 5, 16).to_a.should == - [0, 40, 1, 16, 5, 1940, 4, 137, true, "CEST"] - end + it "uses the 'CET' timezone with TZ=Europe/Amsterdam in 1970" do + with_timezone("Europe/Amsterdam") do + Time.send(@method, 1970, 5, 16).to_a.should == + [0, 0, 0, 16, 5, 1970, 6, 136, false, "CET"] end end end @@ -41,5 +39,4 @@ describe :time_local_10_arg, shared: true do end end end - end diff --git a/spec/ruby/core/time/shared/now.rb b/spec/ruby/core/time/shared/now.rb index c35115fcf4..839cfdcd2a 100644 --- a/spec/ruby/core/time/shared/now.rb +++ b/spec/ruby/core/time/shared/now.rb @@ -1,14 +1,14 @@ -require File.expand_path('../../fixtures/classes', __FILE__) +require_relative '../fixtures/classes' describe :time_now, shared: true do it "creates a subclass instance if called on a subclass" do - TimeSpecs::SubTime.send(@method).should be_an_instance_of(TimeSpecs::SubTime) - TimeSpecs::MethodHolder.send(@method).should be_an_instance_of(Time) + TimeSpecs::SubTime.send(@method).should.instance_of?(TimeSpecs::SubTime) + TimeSpecs::MethodHolder.send(@method).should.instance_of?(Time) end it "sets the current time" do now = TimeSpecs::MethodHolder.send(@method) - now.to_f.should be_close(Process.clock_gettime(Process::CLOCK_REALTIME), 10.0) + now.to_f.should be_close(Process.clock_gettime(Process::CLOCK_REALTIME), TIME_TOLERANCE) end it "uses the local timezone" do @@ -17,4 +17,17 @@ describe :time_now, shared: true do now.utc_offset.should == (-8 * 60 * 60) end end + + it "has at least microsecond precision" do + # The clock should not be less accurate than expected (times should + # not all be a multiple of the next precision up, assuming precisions + # are multiples of ten.) + expected = 1_000 + t = 0 + 10_000.times.find do + t = Time.now.nsec + t % (expected * 10) != 0 + end + (t % (expected * 10)).should != 0 + end end diff --git a/spec/ruby/core/time/shared/time_params.rb b/spec/ruby/core/time/shared/time_params.rb index 120d8d3af1..f0de986b8e 100644 --- a/spec/ruby/core/time/shared/time_params.rb +++ b/spec/ruby/core/time/shared/time_params.rb @@ -30,7 +30,7 @@ describe :time_params, shared: true do end it "raises a TypeError if the year is nil" do - lambda { Time.send(@method, nil) }.should raise_error(TypeError) + -> { Time.send(@method, nil) }.should.raise(TypeError) end it "accepts nil month, day, hour, minute, and second" do @@ -145,46 +145,55 @@ describe :time_params, shared: true do end it "raises an ArgumentError for out of range month" do - lambda { - Time.send(@method, 2008, 13, 31, 23, 59, 59) - }.should raise_error(ArgumentError) + # For some reason MRI uses a different message for month in 13-15 and month>=16 + -> { + Time.send(@method, 2008, 16, 31, 23, 59, 59) + }.should.raise(ArgumentError, /(mon|argument) out of range/) end it "raises an ArgumentError for out of range day" do - lambda { + -> { Time.send(@method, 2008, 12, 32, 23, 59, 59) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises an ArgumentError for out of range hour" do - lambda { + -> { Time.send(@method, 2008, 12, 31, 25, 59, 59) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises an ArgumentError for out of range minute" do - lambda { + -> { Time.send(@method, 2008, 12, 31, 23, 61, 59) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError) end it "raises an ArgumentError for out of range second" do - lambda { + # For some reason MRI uses different messages for seconds 61-63 and seconds >= 64 + -> { Time.send(@method, 2008, 12, 31, 23, 59, 61) - }.should raise_error(ArgumentError) + }.should.raise(ArgumentError, /(sec|argument) out of range/) + -> { + Time.send(@method, 2008, 12, 31, 23, 59, -1) + }.should.raise(ArgumentError, "argument out of range") + end + + it "raises ArgumentError when given 8 arguments" do + -> { Time.send(@method, *[0]*8) }.should.raise(ArgumentError) end it "raises ArgumentError when given 9 arguments" do - lambda { Time.send(@method, *[0]*9) }.should raise_error(ArgumentError) + -> { Time.send(@method, *[0]*9) }.should.raise(ArgumentError) end it "raises ArgumentError when given 11 arguments" do - lambda { Time.send(@method, *[0]*11) }.should raise_error(ArgumentError) + -> { Time.send(@method, *[0]*11) }.should.raise(ArgumentError) end it "returns subclass instances" do c = Class.new(Time) - c.send(@method, 2008, "12").should be_an_instance_of(c) + c.send(@method, 2008, "12").should.instance_of?(c) end end @@ -202,25 +211,25 @@ describe :time_params_10_arg, shared: true do end it "raises an ArgumentError for out of range values" do - lambda { + -> { Time.send(@method, 61, 59, 23, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored) - }.should raise_error(ArgumentError) # sec + }.should.raise(ArgumentError) # sec - lambda { + -> { Time.send(@method, 59, 61, 23, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored) - }.should raise_error(ArgumentError) # min + }.should.raise(ArgumentError) # min - lambda { + -> { Time.send(@method, 59, 59, 25, 31, 12, 2008, :ignored, :ignored, :ignored, :ignored) - }.should raise_error(ArgumentError) # hour + }.should.raise(ArgumentError) # hour - lambda { + -> { Time.send(@method, 59, 59, 23, 32, 12, 2008, :ignored, :ignored, :ignored, :ignored) - }.should raise_error(ArgumentError) # day + }.should.raise(ArgumentError) # day - lambda { + -> { Time.send(@method, 59, 59, 23, 31, 13, 2008, :ignored, :ignored, :ignored, :ignored) - }.should raise_error(ArgumentError) # month + }.should.raise(ArgumentError) # month end end @@ -230,6 +239,10 @@ describe :time_params_microseconds, shared: true do t.usec.should == 123 end + it "raises an ArgumentError for out of range microsecond" do + -> { Time.send(@method, 2000, 1, 1, 20, 15, 1, 1000000) }.should.raise(ArgumentError) + end + it "handles fractional microseconds as a Float" do t = Time.send(@method, 2000, 1, 1, 20, 15, 1, 1.75) t.usec.should == 1 diff --git a/spec/ruby/core/time/shared/to_i.rb b/spec/ruby/core/time/shared/to_i.rb index 03497c700b..06c966b708 100644 --- a/spec/ruby/core/time/shared/to_i.rb +++ b/spec/ruby/core/time/shared/to_i.rb @@ -6,4 +6,11 @@ describe :time_to_i, shared: true do it "doesn't return an actual number of seconds in time" do Time.at(65.5).send(@method).should == 65 end + + it "rounds fractional seconds toward zero" do + t = Time.utc(1960, 1, 1, 0, 0, 0, 999_999) + + t.to_f.to_i.should == -315619199 + t.to_i.should == -315619200 + end end diff --git a/spec/ruby/core/time/shared/xmlschema.rb b/spec/ruby/core/time/shared/xmlschema.rb new file mode 100644 index 0000000000..d68c18df36 --- /dev/null +++ b/spec/ruby/core/time/shared/xmlschema.rb @@ -0,0 +1,31 @@ +describe :time_xmlschema, shared: true do + ruby_version_is "3.4" do + it "generates ISO-8601 strings in Z for UTC times" do + t = Time.utc(1985, 4, 12, 23, 20, 50, 521245) + t.send(@method).should == "1985-04-12T23:20:50Z" + t.send(@method, 2).should == "1985-04-12T23:20:50.52Z" + t.send(@method, 9).should == "1985-04-12T23:20:50.521245000Z" + end + + it "generates ISO-8601 string with timeone offset for non-UTC times" do + t = Time.new(1985, 4, 12, 23, 20, 50, "+02:00") + t.send(@method).should == "1985-04-12T23:20:50+02:00" + t.send(@method, 2).should == "1985-04-12T23:20:50.00+02:00" + end + + it "year is always at least 4 digits" do + t = Time.utc(12, 4, 12) + t.send(@method).should == "0012-04-12T00:00:00Z" + end + + it "year can be more than 4 digits" do + t = Time.utc(40_000, 4, 12) + t.send(@method).should == "40000-04-12T00:00:00Z" + end + + it "year can be negative" do + t = Time.utc(-2000, 4, 12) + t.send(@method).should == "-2000-04-12T00:00:00Z" + end + end +end diff --git a/spec/ruby/core/time/strftime_spec.rb b/spec/ruby/core/time/strftime_spec.rb index 1cb3575eec..1528a668a1 100644 --- a/spec/ruby/core/time/strftime_spec.rb +++ b/spec/ruby/core/time/strftime_spec.rb @@ -1,19 +1,19 @@ # encoding: utf-8 -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../shared/time/strftime_for_date', __FILE__) -require File.expand_path('../../../shared/time/strftime_for_time', __FILE__) +require_relative '../../spec_helper' +require_relative '../../shared/time/strftime_for_date' +require_relative '../../shared/time/strftime_for_time' describe "Time#strftime" do before :all do - @new_date = lambda { |y,m,d| Time.gm(y,m,d) } - @new_time = lambda { |*args| Time.gm(*args) } - @new_time_in_zone = lambda { |zone,offset,*args| + @new_date = -> y, m, d { Time.gm(y,m,d) } + @new_time = -> *args { Time.gm(*args) } + @new_time_in_zone = -> zone, offset, *args { with_timezone(zone, offset) do Time.new(*args) end } - @new_time_with_offset = lambda { |y,m,d,h,min,s,offset| + @new_time_with_offset = -> y, m, d, h, min, s, offset { Time.new(y,m,d,h,min,s,offset) } @@ -25,7 +25,7 @@ describe "Time#strftime" do # Differences with date it "requires an argument" do - lambda { @time.strftime }.should raise_error(ArgumentError) + -> { @time.strftime }.should.raise(ArgumentError) end # %Z is zone name or empty for Time @@ -49,4 +49,43 @@ describe "Time#strftime" do time = @new_time_with_offset[2012, 1, 1, 0, 0, 0, Rational(36645, 10)] time.strftime("%::z").should == "+01:01:05" end + + it "supports RFC 3339 UTC for unknown offset local time, -0000, as %-z" do + time = Time.gm(2022) + + time.strftime("%z").should == "+0000" + time.strftime("%-z").should == "-0000" + time.strftime("%-:z").should == "-00:00" + time.strftime("%-::z").should == "-00:00:00" + end + + it "applies '-' flag to UTC time" do + time = Time.utc(2022) + time.strftime("%-z").should == "-0000" + + time = Time.gm(2022) + time.strftime("%-z").should == "-0000" + + time = Time.new(2022, 1, 1, 0, 0, 0, "Z") + time.strftime("%-z").should == "-0000" + + time = Time.new(2022, 1, 1, 0, 0, 0, "-00:00") + time.strftime("%-z").should == "-0000" + + time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00").utc + time.strftime("%-z").should == "-0000" + end + + it "ignores '-' flag for non-UTC time" do + time = Time.new(2022, 1, 1, 0, 0, 0, "+03:00") + time.strftime("%-z").should == "+0300" + end + + it "works correctly with width, _ and 0 flags, and :" do + Time.now.utc.strftime("%-_10z").should == " -000" + Time.now.utc.strftime("%-10z").should == "-000000000" + Time.now.utc.strftime("%-010:z").should == "-000000:00" + Time.now.utc.strftime("%-_10:z").should == " -0:00" + Time.now.utc.strftime("%-_10::z").should == " -0:00:00" + end end diff --git a/spec/ruby/core/time/subsec_spec.rb b/spec/ruby/core/time/subsec_spec.rb index d9d262a513..3ed1bf5dd1 100644 --- a/spec/ruby/core/time/subsec_spec.rb +++ b/spec/ruby/core/time/subsec_spec.rb @@ -1,27 +1,27 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#subsec" do - it "returns 0 as a Fixnum for a Time with a whole number of seconds" do - Time.at(100).subsec.should eql(0) + it "returns 0 as an Integer for a Time with a whole number of seconds" do + Time.at(100).subsec.should.eql?(0) end it "returns the fractional seconds as a Rational for a Time constructed with a Rational number of seconds" do - Time.at(Rational(3, 2)).subsec.should eql(Rational(1, 2)) + Time.at(Rational(3, 2)).subsec.should.eql?(Rational(1, 2)) end it "returns the fractional seconds as a Rational for a Time constructed with a Float number of seconds" do - Time.at(10.75).subsec.should eql(Rational(3, 4)) + Time.at(10.75).subsec.should.eql?(Rational(3, 4)) end it "returns the fractional seconds as a Rational for a Time constructed with an Integer number of microseconds" do - Time.at(0, 999999).subsec.should eql(Rational(999999, 1000000)) + Time.at(0, 999999).subsec.should.eql?(Rational(999999, 1000000)) end it "returns the fractional seconds as a Rational for a Time constructed with an Rational number of microseconds" do - Time.at(0, Rational(9, 10)).subsec.should eql(Rational(9, 10000000)) + Time.at(0, Rational(9, 10)).subsec.should.eql?(Rational(9, 10000000)) end it "returns the fractional seconds as a Rational for a Time constructed with an Float number of microseconds" do - Time.at(0, 0.75).subsec.should eql(Rational(3, 4000000)) + Time.at(0, 0.75).subsec.should.eql?(Rational(3, 4000000)) end end diff --git a/spec/ruby/core/time/succ_spec.rb b/spec/ruby/core/time/succ_spec.rb deleted file mode 100644 index 6831200741..0000000000 --- a/spec/ruby/core/time/succ_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.expand_path('../../../spec_helper', __FILE__) - -describe "Time#succ" do - it "returns a new time one second later than time" do - -> { - @result = Time.at(100).succ - }.should complain(/Time#succ is obsolete/) - @result.should == Time.at(101) - end - - it "returns a new instance" do - t1 = Time.at(100) - t2 = nil - -> { - t2 = t1.succ - }.should complain(/Time#succ is obsolete/) - t1.object_id.should_not == t2.object_id - end -end diff --git a/spec/ruby/core/time/sunday_spec.rb b/spec/ruby/core/time/sunday_spec.rb index 823174cf71..0d46421132 100644 --- a/spec/ruby/core/time/sunday_spec.rb +++ b/spec/ruby/core/time/sunday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#sunday?" do it "returns true if time represents Sunday" do - Time.local(2000, 1, 2).sunday?.should == true + Time.local(2000, 1, 2).should.sunday? end it "returns false if time doesn't represent Sunday" do - Time.local(2000, 1, 1).sunday?.should == false + Time.local(2000, 1, 1).should_not.sunday? end end diff --git a/spec/ruby/core/time/thursday_spec.rb b/spec/ruby/core/time/thursday_spec.rb index 5788fd9bc7..c11e79d2fa 100644 --- a/spec/ruby/core/time/thursday_spec.rb +++ b/spec/ruby/core/time/thursday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#thursday?" do it "returns true if time represents Thursday" do - Time.local(2000, 1, 6).thursday?.should == true + Time.local(2000, 1, 6).should.thursday? end it "returns false if time doesn't represent Thursday" do - Time.local(2000, 1, 1).thursday?.should == false + Time.local(2000, 1, 1).should_not.thursday? end end diff --git a/spec/ruby/core/time/time_spec.rb b/spec/ruby/core/time/time_spec.rb index 1c870c8e0f..b0803a7f21 100644 --- a/spec/ruby/core/time/time_spec.rb +++ b/spec/ruby/core/time/time_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time" do it "includes Comparable" do diff --git a/spec/ruby/core/time/to_a_spec.rb b/spec/ruby/core/time/to_a_spec.rb index a4c4a8fbc9..3728b8c526 100644 --- a/spec/ruby/core/time/to_a_spec.rb +++ b/spec/ruby/core/time/to_a_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#to_a" do platform_is_not :windows do diff --git a/spec/ruby/core/time/to_f_spec.rb b/spec/ruby/core/time/to_f_spec.rb index d737848b4b..6101dcf871 100644 --- a/spec/ruby/core/time/to_f_spec.rb +++ b/spec/ruby/core/time/to_f_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#to_f" do it "returns the float number of seconds + usecs since the epoch" do diff --git a/spec/ruby/core/time/to_i_spec.rb b/spec/ruby/core/time/to_i_spec.rb index 1a733f02cf..54929d1e18 100644 --- a/spec/ruby/core/time/to_i_spec.rb +++ b/spec/ruby/core/time/to_i_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/to_i', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/to_i' describe "Time#to_i" do - it_behaves_like(:time_to_i, :to_i) + it_behaves_like :time_to_i, :to_i end diff --git a/spec/ruby/core/time/to_r_spec.rb b/spec/ruby/core/time/to_r_spec.rb index 53e469463a..e30f5d8f94 100644 --- a/spec/ruby/core/time/to_r_spec.rb +++ b/spec/ruby/core/time/to_r_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#to_r" do it "returns the a Rational representing seconds and subseconds since the epoch" do - Time.at(Rational(11, 10)).to_r.should eql(Rational(11, 10)) + Time.at(Rational(11, 10)).to_r.should.eql?(Rational(11, 10)) end it "returns a Rational even for a whole number of seconds" do - Time.at(2).to_r.should eql(Rational(2)) + Time.at(2).to_r.should.eql?(Rational(2)) end end diff --git a/spec/ruby/core/time/to_s_spec.rb b/spec/ruby/core/time/to_s_spec.rb index 8dc81f60a9..ac6c0908ac 100644 --- a/spec/ruby/core/time/to_s_spec.rb +++ b/spec/ruby/core/time/to_s_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/inspect', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/inspect' describe "Time#to_s" do it_behaves_like :inspect, :to_s diff --git a/spec/ruby/core/time/tuesday_spec.rb b/spec/ruby/core/time/tuesday_spec.rb index 87c3236eea..0e7b9e7506 100644 --- a/spec/ruby/core/time/tuesday_spec.rb +++ b/spec/ruby/core/time/tuesday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#tuesday?" do it "returns true if time represents Tuesday" do - Time.local(2000, 1, 4).tuesday?.should == true + Time.local(2000, 1, 4).should.tuesday? end it "returns false if time doesn't represent Tuesday" do - Time.local(2000, 1, 1).tuesday?.should == false + Time.local(2000, 1, 1).should_not.tuesday? end end diff --git a/spec/ruby/core/time/tv_nsec_spec.rb b/spec/ruby/core/time/tv_nsec_spec.rb index d477f6fbec..feb7998b16 100644 --- a/spec/ruby/core/time/tv_nsec_spec.rb +++ b/spec/ruby/core/time/tv_nsec_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#tv_nsec" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/time/tv_sec_spec.rb b/spec/ruby/core/time/tv_sec_spec.rb index 36f090be7b..f83e1fbfdd 100644 --- a/spec/ruby/core/time/tv_sec_spec.rb +++ b/spec/ruby/core/time/tv_sec_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/to_i', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/to_i' describe "Time#tv_sec" do - it_behaves_like(:time_to_i, :tv_sec) + it_behaves_like :time_to_i, :tv_sec end diff --git a/spec/ruby/core/time/tv_usec_spec.rb b/spec/ruby/core/time/tv_usec_spec.rb index 4a1b87be7a..f0de4f4a9c 100644 --- a/spec/ruby/core/time/tv_usec_spec.rb +++ b/spec/ruby/core/time/tv_usec_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#tv_usec" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/time/usec_spec.rb b/spec/ruby/core/time/usec_spec.rb index 018253ec77..6ea52f5e79 100644 --- a/spec/ruby/core/time/usec_spec.rb +++ b/spec/ruby/core/time/usec_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#usec" do it "returns 0 for a Time constructed with a whole number of seconds" do @@ -36,4 +36,8 @@ describe "Time#usec" do it "returns the microseconds for time created by Time#local" do Time.local(1,2,3,4,5,Rational(6.78)).usec.should == 780000 end + + it "returns a positive value for dates before the epoch" do + Time.utc(1969, 11, 12, 13, 18, 57, 404240).usec.should == 404240 + end end diff --git a/spec/ruby/core/time/utc_offset_spec.rb b/spec/ruby/core/time/utc_offset_spec.rb index 4be885f2e3..17c031b8c6 100644 --- a/spec/ruby/core/time/utc_offset_spec.rb +++ b/spec/ruby/core/time/utc_offset_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gmt_offset', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gmt_offset' describe "Time#utc_offset" do - it_behaves_like(:time_gmt_offset, :utc_offset) + it_behaves_like :time_gmt_offset, :utc_offset end diff --git a/spec/ruby/core/time/utc_spec.rb b/spec/ruby/core/time/utc_spec.rb index f88b9c7cbc..ab3c0df657 100644 --- a/spec/ruby/core/time/utc_spec.rb +++ b/spec/ruby/core/time/utc_spec.rb @@ -1,21 +1,66 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../shared/gm', __FILE__) -require File.expand_path('../shared/gmtime', __FILE__) -require File.expand_path('../shared/time_params', __FILE__) +require_relative '../../spec_helper' +require_relative 'shared/gm' +require_relative 'shared/gmtime' +require_relative 'shared/time_params' describe "Time#utc?" do - it "returns true if time represents a time in UTC (GMT)" do + it "returns true only if time represents a time in UTC (GMT)" do Time.now.utc?.should == false + Time.now.utc.utc?.should == true + end + + it "treats time as UTC what was created in different ways" do + Time.now.utc.utc?.should == true + Time.now.gmtime.utc?.should == true + Time.now.getgm.utc?.should == true + Time.now.getutc.utc?.should == true + Time.utc(2022).utc?.should == true + end + + it "does treat time with 'UTC' offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, "UTC").utc?.should == true + Time.now.localtime("UTC").utc?.should == true + Time.at(Time.now, in: 'UTC').utc?.should == true + + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").utc?.should == true + Time.now(in: "UTC").utc?.should == true + end + + it "does treat time with Z offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, "Z").utc?.should == true + Time.now.localtime("Z").utc?.should == true + Time.at(Time.now, in: 'Z').utc?.should == true + + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").utc?.should == true + Time.now(in: "Z").utc?.should == true + end + + it "does treat time with -00:00 offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, "-00:00").utc?.should == true + Time.now.localtime("-00:00").utc?.should == true + Time.at(Time.now, in: '-00:00').utc?.should == true + end + + it "does not treat time with +00:00 offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, "+00:00").utc?.should == false + Time.now.localtime("+00:00").utc?.should == false + Time.at(Time.now, in: "+00:00").utc?.should == false + end + + it "does not treat time with 0 offset as UTC" do + Time.new(2022, 1, 1, 0, 0, 0, 0).utc?.should == false + Time.now.localtime(0).utc?.should == false + Time.at(Time.now, in: 0).utc?.should == false end end describe "Time.utc" do - it_behaves_like(:time_gm, :utc) - it_behaves_like(:time_params, :utc) - it_behaves_like(:time_params_10_arg, :utc) - it_behaves_like(:time_params_microseconds, :utc) + it_behaves_like :time_gm, :utc + it_behaves_like :time_params, :utc + it_behaves_like :time_params_10_arg, :utc + it_behaves_like :time_params_microseconds, :utc end describe "Time#utc" do - it_behaves_like(:time_gmtime, :utc) + it_behaves_like :time_gmtime, :utc end diff --git a/spec/ruby/core/time/wday_spec.rb b/spec/ruby/core/time/wday_spec.rb index 72bc718356..9f63f67de9 100644 --- a/spec/ruby/core/time/wday_spec.rb +++ b/spec/ruby/core/time/wday_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#wday" do it "returns an integer representing the day of the week, 0..6, with Sunday being 0" do diff --git a/spec/ruby/core/time/wednesday_spec.rb b/spec/ruby/core/time/wednesday_spec.rb index 8e836e3e02..cc686681d7 100644 --- a/spec/ruby/core/time/wednesday_spec.rb +++ b/spec/ruby/core/time/wednesday_spec.rb @@ -1,11 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#wednesday?" do it "returns true if time represents Wednesday" do - Time.local(2000, 1, 5).wednesday?.should == true + Time.local(2000, 1, 5).should.wednesday? end it "returns false if time doesn't represent Wednesday" do - Time.local(2000, 1, 1).wednesday?.should == false + Time.local(2000, 1, 1).should_not.wednesday? end end diff --git a/spec/ruby/core/time/xmlschema_spec.rb b/spec/ruby/core/time/xmlschema_spec.rb new file mode 100644 index 0000000000..bdf1dc7923 --- /dev/null +++ b/spec/ruby/core/time/xmlschema_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/xmlschema' + +describe "Time#xmlschema" do + it_behaves_like :time_xmlschema, :xmlschema +end diff --git a/spec/ruby/core/time/yday_spec.rb b/spec/ruby/core/time/yday_spec.rb index 2b7aca1565..e920c2e28d 100644 --- a/spec/ruby/core/time/yday_spec.rb +++ b/spec/ruby/core/time/yday_spec.rb @@ -1,4 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' +require_relative '../../shared/time/yday' describe "Time#yday" do it "returns an integer representing the day of the year, 1..366" do @@ -7,15 +8,5 @@ describe "Time#yday" do end end - it 'returns the correct value for each day of each month' do - mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] - - yday = 1 - mdays.each_with_index do |days, month| - days.times do |day| - Time.new(2014, month+1, day+1).yday.should == yday - yday += 1 - end - end - end + it_behaves_like :time_yday, -> year, month, day { Time.new(year, month, day).yday } end diff --git a/spec/ruby/core/time/year_spec.rb b/spec/ruby/core/time/year_spec.rb index 4e18eb1353..d2d50062c5 100644 --- a/spec/ruby/core/time/year_spec.rb +++ b/spec/ruby/core/time/year_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#year" do it "returns the four digit year for a local Time as an Integer" do diff --git a/spec/ruby/core/time/zone_spec.rb b/spec/ruby/core/time/zone_spec.rb index 9c7acd66e3..2cb3c5e7bb 100644 --- a/spec/ruby/core/time/zone_spec.rb +++ b/spec/ruby/core/time/zone_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Time#zone" do platform_is_not :windows do @@ -6,7 +6,7 @@ describe "Time#zone" do with_timezone("America/New_York") do Time.new(2001, 1, 1, 0, 0, 0).zone.should == "EST" Time.new(2001, 7, 1, 0, 0, 0).zone.should == "EDT" - %w[EST EDT].should include Time.now.zone + %w[EST EDT].should.include? Time.now.zone end end end @@ -29,7 +29,7 @@ describe "Time#zone" do t = Time.new(2005, 2, 27, 22, 50, 0, -3600) with_timezone("America/New_York") do - t.getlocal("+05:00").zone.should be_nil + t.getlocal("+05:00").zone.should == nil end end @@ -52,14 +52,35 @@ describe "Time#zone" do end it "doesn't raise errors for a Time with a fixed offset" do - lambda { - Time.new(2001, 1, 1, 0, 0, 0, "+05:00").zone - }.should_not raise_error + Time.new(2001, 1, 1, 0, 0, 0, "+05:00").zone.should == nil end end it "returns UTC when called on a UTC time" do Time.now.utc.zone.should == "UTC" + Time.now.gmtime.zone.should == "UTC" + Time.now.getgm.zone.should == "UTC" + Time.now.getutc.zone.should == "UTC" + Time.utc(2022).zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, "UTC").zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, "Z").zone.should == "UTC" + Time.now.localtime("UTC").zone.should == "UTC" + Time.now.localtime("Z").zone.should == "UTC" + Time.at(Time.now, in: 'UTC').zone.should == "UTC" + Time.at(Time.now, in: 'Z').zone.should == "UTC" + + Time.new(2022, 1, 1, 0, 0, 0, "-00:00").zone.should == "UTC" + Time.now.localtime("-00:00").zone.should == "UTC" + Time.at(Time.now, in: '-00:00').zone.should == "UTC" + + Time.new(2022, 1, 1, 0, 0, 0, in: "UTC").zone.should == "UTC" + Time.new(2022, 1, 1, 0, 0, 0, in: "Z").zone.should == "UTC" + + Time.now(in: 'UTC').zone.should == "UTC" + Time.now(in: 'Z').zone.should == "UTC" + + Time.at(Time.now, in: 'UTC').zone.should == "UTC" + Time.at(Time.now, in: 'Z').zone.should == "UTC" end platform_is_not :aix, :windows do |
