TimeWithZone caches TZInfo::TimezonePeriod used for time conversion so that it can be reused, and enforces DST rules correctly when instance is created from a local time

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9040 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Geoff Buesing 2008-03-17 02:40:28 +00:00
parent 1b41178a83
commit 77ee522bf6
4 changed files with 55 additions and 16 deletions

@ -1,5 +1,7 @@
*SVN*
* TimeWithZone caches TZInfo::TimezonePeriod used for time conversion so that it can be reused, and enforces DST rules correctly when instance is created from a local time [Geoff Buesing]
* Fixed that BufferedLogger should create its own directory if one doesn't already exist #11285 [lotswholetime]
* Fix Numeric time tests broken by DST change by anchoring them to fixed times instead of Time.now. Anchor TimeZone#now DST test to time specified with Time.at instead of Time.local to work around platform differences with Time.local and DST representation [Geoff Buesing]

@ -5,29 +5,28 @@ class TimeWithZone
include Comparable
attr_reader :time_zone
def initialize(utc_time, time_zone, local_time = nil)
@utc = utc_time
@time = local_time
@time_zone = time_zone
def initialize(utc_time, time_zone, local_time = nil, period = nil)
@utc, @time_zone, @time = utc_time, time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time
end
# Returns a Time instance that represents the time in time_zone
def time
@time ||= time_zone.utc_to_local(@utc)
@time ||= utc_to_local
end
# Returns a Time instance that represents the time in UTC
def utc
@utc ||= time_zone.local_to_utc(@time)
@utc ||= local_to_utc
end
alias_method :comparable_time, :utc
alias_method :getgm, :utc
alias_method :getutc, :utc
alias_method :gmtime, :utc
# Returns the underlying TZInfo::TimezonePeriod for the local time
# Returns the underlying TZInfo::TimezonePeriod
def period
@period ||= time_zone.period_for_utc(utc)
@period ||= time_zone.period_for_utc(@utc)
end
# Returns the simultaneous time in the specified zone
@ -214,5 +213,24 @@ def method_missing(sym, *args, &block)
result = result.in_time_zone(time_zone) if result.acts_like?(:time)
result
end
private
def get_period_and_ensure_valid_local_time
@time_zone.period_for_local(@time)
rescue ::TZInfo::PeriodNotFound
# time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
@time += 1.hour
retry
end
# Replicating logic from TZInfo::Timezone#utc_to_local because we want to cache the period in an instance variable for reuse
def utc_to_local
::TZInfo::TimeOrDateTime.wrap(utc) {|utc| period.to_local(utc)}
end
# Replicating logic from TZInfo::Timezone#local_to_utc because we want to cache the period in an instance variable for reuse
def local_to_utc
::TZInfo::TimeOrDateTime.wrap(time) {|time| period.to_utc(time)}
end
end
end

@ -183,14 +183,8 @@ def to_s
# Time.zone = "Hawaii" # => "Hawaii"
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
def local(*args)
t = Time.utc_time(*args)
begin
result = local_to_utc(t)
rescue TZInfo::PeriodNotFound
t += 1.hour
retry
end
result.in_time_zone(self)
time = Time.utc_time(*args)
ActiveSupport::TimeWithZone.new(nil, self, time)
end
# Returns an ActiveSupport::TimeWithZone instance representing the current time

@ -256,6 +256,31 @@ def test_method_missing_with_non_time_return_value
assert_equal 17, twz.sec
assert_equal 500, twz.usec
end
def test_utc_to_local_conversion_saves_period_in_instance_variable
assert_nil @twz.instance_variable_get('@period')
@twz.time
assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get('@period')
end
def test_instance_created_with_local_time_returns_correct_utc_time
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(1999, 12, 31, 19))
assert_equal Time.utc(2000), twz.utc
end
def test_instance_created_with_local_time_enforces_spring_dst_rules
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST
assert_equal Time.utc(2006,4,2,3), twz.time # springs forward to 3AM
assert_equal true, twz.dst?
assert_equal 'EDT', twz.zone
end
def test_instance_created_with_local_time_enforces_fall_dst_rules
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,1)) # 1AM can be either DST or non-DST; we'll pick DST
assert_equal Time.utc(2006,10,29,1), twz.time
assert_equal true, twz.dst?
assert_equal 'EDT', twz.zone
end
end
class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase