1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Add ActiveSupport::TimeZone.iso8601 parsing method

Previously there was no way to get a ISO 8601 timestamp into a specific
timezone without either using `parse` or chaining methods. The new method
allows parsing directly into the timezone, e.g:

    >> Time.zone = "Hawaii"
    => "Hawaii"
    >> Time.zone.iso8601("1999-12-31T14:00:00Z")
    => Fri, 31 Dec 1999 14:00:00 HST -10:00

If the timestamp is a ISO 8601 date (YYYY-MM-DD) then the time is set
to midnight, e.g:

    >> Time.zone = "Hawaii"
    => "Hawaii"
    >> Time.zone.iso8601("1999-12-31")
    => Fri, 31 Dec 1999 00:00:00 HST -10:00

This new method has stricter semantics than the current `parse` method
and will raise an `ArgumentError` instead of returning nil, e.g:

    >> Time.zone = "Hawaii"
    => "Hawaii"
    >> Time.zone.iso8601("foobar")
    ArgumentError: invalid date
    >> Time.zone.parse("foobar")
    => nil
This commit is contained in:
Andrew White 2017-03-03 14:58:35 +00:00
parent d3e7d91050
commit 4974b1a483
3 changed files with 138 additions and 0 deletions

View file

@ -1,3 +1,34 @@
* Add `ActiveSupport::TimeZone.iso8601` parsing method
Previously there was no way to get a ISO 8601 timestamp into a specific
timezone without either using `parse` or chaining methods. The new method
allows parsing directly into the timezone, e.g:
>> Time.zone = "Hawaii"
=> "Hawaii"
>> Time.zone.iso8601("1999-12-31T14:00:00Z")
=> Fri, 31 Dec 1999 14:00:00 HST -10:00
If the timestamp is a ISO 8601 date (YYYY-MM-DD) then the time is set
to midnight, e.g:
>> Time.zone = "Hawaii"
=> "Hawaii"
>> Time.zone.iso8601("1999-12-31")
=> Fri, 31 Dec 1999 00:00:00 HST -10:00
This new method has stricter semantics than the current `parse` method
and will raise an `ArgumentError` instead of returning nil, e.g:
>> Time.zone = "Hawaii"
=> "Hawaii"
>> Time.zone.iso8601("foobar")
ArgumentError: invalid date
>> Time.zone.parse("foobar")
=> nil
*Andrew White*
* Deprecate implicit coercion of `ActiveSupport::Duration`
Currently `ActiveSupport::Duration` implicitly converts to a seconds

View file

@ -339,6 +339,41 @@ module ActiveSupport
Time.at(secs).utc.in_time_zone(self)
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
# of +self+ from an ISO 8601 string.
#
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
#
# If the time components are missing then they will be set to zero.
#
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00
#
# If the string is invalid then an +ArgumentError+ will be raised unlike +parse+
# which returns +nil+ when given an invalid date string.
def iso8601(str)
parts = Date._iso8601(str)
raise ArgumentError, "invalid date" if parts.empty?
time = Time.new(
parts.fetch(:year),
parts.fetch(:mon),
parts.fetch(:mday),
parts.fetch(:hour, 0),
parts.fetch(:min, 0),
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
parts.fetch(:offset, 0)
)
if parts[:offset]
TimeWithZone.new(time.utc, self)
else
TimeWithZone.new(nil, self, time)
end
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
# of +self+ from parsed string.
#

View file

@ -215,6 +215,78 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal secs, twz.to_f
end
def test_iso8601
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.iso8601("1999-12-31T19:00:00")
assert_equal Time.utc(1999, 12, 31, 19), twz.time
assert_equal Time.utc(2000), twz.utc
assert_equal zone, twz.time_zone
end
def test_iso8601_with_invalid_string
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
exception = assert_raises(ArgumentError) do
zone.iso8601("foobar")
end
assert_equal "invalid date", exception.message
end
def test_iso8601_with_missing_time_components
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.iso8601("1999-12-31")
assert_equal Time.utc(1999, 12, 31, 0, 0, 0), twz.time
assert_equal Time.utc(1999, 12, 31, 5, 0, 0), twz.utc
assert_equal zone, twz.time_zone
end
def test_iso8601_with_old_date
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.iso8601("1883-12-31T19:00:00")
assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
assert_equal zone, twz.time_zone
end
def test_iso8601_far_future_date_with_time_zone_offset_in_string
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.iso8601("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
assert_equal zone, twz.time_zone
end
def test_iso8601_should_not_black_out_system_timezone_dst_jump
with_env_tz("EET") do
zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
twz = zone.iso8601("2012-03-25T03:29:00")
assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
end
end
def test_iso8601_should_black_out_app_timezone_dst_jump
with_env_tz("EET") do
zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
twz = zone.iso8601("2012-03-11T02:29:00")
assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
end
end
def test_iso8601_doesnt_use_local_dst
with_env_tz "US/Eastern" do
zone = ActiveSupport::TimeZone["UTC"]
twz = zone.iso8601("2013-03-10T02:00:00")
assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
end
end
def test_iso8601_handles_dst_jump
with_env_tz "US/Eastern" do
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.iso8601("2013-03-10T02:00:00")
assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
end
end
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")