diff options
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/values/time_zone.rb | 27 | ||||
-rw-r--r-- | activesupport/test/time_zone_test.rb | 70 |
3 files changed, 83 insertions, 16 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index ebb25b54e6..f72484d36d 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* TimeZone #local and #now correctly enforce DST rules [Geoff Buesing] + * TimeWithZone instances correctly enforce DST rules. Adding TimeZone#period_for_utc [Geoff Buesing] * test_time_with_datetime_fallback expects DateTime.local_offset instead of DateTime.now.offset [Geoff Buesing] diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index ced823f052..a52eee3e7c 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -174,25 +174,32 @@ class TimeZone def to_s "(UTC#{formatted_offset}) #{name}" end - - # Method for creating new ActiveSupport::TimeWithZone instance in time zone of self. Example: - # - # 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) - Time.utc_time(*args).change_time_zone(self) - end begin # the following methods depend on the tzinfo gem require_library_or_gem "tzinfo" unless Object.const_defined?(:TZInfo) + # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+. Example: + # + # 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) + end + # Returns an ActiveSupport::TimeWithZone instance representing the current time # in the time zone represented by +self+. Example: # # Time.zone = 'Hawaii' # => "Hawaii" # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 def now - tzinfo.now.change_time_zone(self) + Time.now.utc.in_time_zone(self) end # Return the current date in this time zone. @@ -233,7 +240,7 @@ class TimeZone rescue LoadError # Tzinfo gem is not available # re-raise LoadError only when a tzinfo-dependent method is called: - %w(now today utc_to_local local_to_utc period_for_local tzinfo).each do |method| + %w(local now today utc_to_local local_to_utc period_for_utc period_for_local tzinfo).each do |method| define_method(method) {|*args| raise LoadError, "TZInfo gem is required for TimeZone##{method}. `gem install tzinfo` and try again."} end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 43f60690d9..de06482af6 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -62,11 +62,32 @@ class TimeZoneTest < Test::Unit::TestCase uses_mocha 'TestTimeZoneNowAndToday' do def test_now - TZInfo::DataTimezone.any_instance.stubs(:now).returns(Time.utc(2000)) - zone = TimeZone['Eastern Time (US & Canada)'] - assert_instance_of ActiveSupport::TimeWithZone, zone.now - assert_equal Time.utc(2000), zone.now.time - assert_equal zone, zone.now.time_zone + with_env_tz 'US/Eastern' do + Time.stubs(:now).returns(Time.local(2000)) + zone = TimeZone['Eastern Time (US & Canada)'] + assert_instance_of ActiveSupport::TimeWithZone, zone.now + assert_equal Time.utc(2000,1,1,5), zone.now.utc + assert_equal Time.utc(2000), zone.now.time + assert_equal zone, zone.now.time_zone + end + end + + def test_now_enforces_spring_dst_rules + with_env_tz 'US/Eastern' do + Time.stubs(:now).returns(Time.local(2006,4,2,2)) # 2AM springs forward to 3AM + zone = TimeZone['Eastern Time (US & Canada)'] + assert_equal Time.utc(2006,4,2,3), zone.now.time + assert_equal true, zone.now.dst? + end + end + + def test_now_enforces_fall_dst_rules + with_env_tz 'US/Eastern' do + Time.stubs(:now).returns(Time.local(2006,10,29,1)) # 1AM is ambiguous; could be DST or non-DST 1AM + zone = TimeZone['Eastern Time (US & Canada)'] + assert_equal Time.utc(2006,10,29,1), zone.now.time # selects DST 1AM + assert_equal true, zone.now.dst? + end end def test_today @@ -134,5 +155,42 @@ class TimeZoneTest < Test::Unit::TestCase assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time assert_equal TimeZone["Hawaii"], time.time_zone end -end + + def test_local_enforces_spring_dst_rules + zone = TimeZone['Eastern Time (US & Canada)'] + twz = zone.local(2006,4,2,1,59,59) # 1 second before DST start + assert_equal Time.utc(2006,4,2,1,59,59), twz.time + assert_equal Time.utc(2006,4,2,6,59,59), twz.utc + assert_equal false, twz.dst? + assert_equal 'EST', twz.zone + twz2 = zone.local(2006,4,2,2) # 2AM does not exist because at 2AM, time springs forward to 3AM + assert_equal Time.utc(2006,4,2,3), twz2.time # twz is created for 3AM + assert_equal Time.utc(2006,4,2,7), twz2.utc + assert_equal true, twz2.dst? + assert_equal 'EDT', twz2.zone + twz3 = zone.local(2006,4,2,2,30) # 2:30AM does not exist because at 2AM, time springs forward to 3AM + assert_equal Time.utc(2006,4,2,3,30), twz3.time # twz is created for 3:30AM + assert_equal Time.utc(2006,4,2,7,30), twz3.utc + assert_equal true, twz3.dst? + assert_equal 'EDT', twz3.zone + end + + def test_local_enforces_fall_dst_rules + # 1AM during fall DST transition is ambiguous, it could be either DST or non-DST 1AM + # Mirroring Time.local behavior, this method selects the DST time + zone = TimeZone['Eastern Time (US & Canada)'] + twz = zone.local(2006,10,29,1) + assert_equal Time.utc(2006,10,29,1), twz.time + assert_equal Time.utc(2006,10,29,5), twz.utc + assert_equal true, twz.dst? + assert_equal 'EDT', twz.zone + end + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end +end |