Skip to content
This repository
Browse code

TimeWithZone caches TZInfo::TimezonePeriod used for time conversion s…

…o 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
  • Loading branch information...
commit 77ee522bf6c97352485fad3b72866f7ab424ecaf 1 parent 1b41178
Geoff Buesing authored March 17, 2008
2  activesupport/CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 *SVN*
2 2
 
  3
+* 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]
  4
+
3 5
 * Fixed that BufferedLogger should create its own directory if one doesn't already exist #11285 [lotswholetime]
4 6
 
5 7
 * 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]
34  activesupport/lib/active_support/time_with_zone.rb
@@ -5,29 +5,28 @@ class TimeWithZone
5 5
     include Comparable
6 6
     attr_reader :time_zone
7 7
   
8  
-    def initialize(utc_time, time_zone, local_time = nil)
9  
-      @utc = utc_time
10  
-      @time = local_time
11  
-      @time_zone = time_zone
  8
+    def initialize(utc_time, time_zone, local_time = nil, period = nil)
  9
+      @utc, @time_zone, @time = utc_time, time_zone, local_time
  10
+      @period = @utc ? period : get_period_and_ensure_valid_local_time
12 11
     end
13 12
   
14 13
     # Returns a Time instance that represents the time in time_zone
15 14
     def time
16  
-      @time ||= time_zone.utc_to_local(@utc)
  15
+      @time ||= utc_to_local
17 16
     end
18 17
 
19 18
     # Returns a Time instance that represents the time in UTC
20 19
     def utc
21  
-      @utc ||= time_zone.local_to_utc(@time)
  20
+      @utc ||= local_to_utc
22 21
     end
23 22
     alias_method :comparable_time, :utc
24 23
     alias_method :getgm, :utc
25 24
     alias_method :getutc, :utc
26 25
     alias_method :gmtime, :utc
27 26
   
28  
-    # Returns the underlying TZInfo::TimezonePeriod for the local time
  27
+    # Returns the underlying TZInfo::TimezonePeriod
29 28
     def period
30  
-      @period ||= time_zone.period_for_utc(utc)
  29
+      @period ||= time_zone.period_for_utc(@utc)
31 30
     end
32 31
 
33 32
     # Returns the simultaneous time in the specified zone
@@ -214,5 +213,24 @@ def method_missing(sym, *args, &block)
214 213
       result = result.in_time_zone(time_zone) if result.acts_like?(:time)
215 214
       result
216 215
     end
  216
+    
  217
+    private      
  218
+      def get_period_and_ensure_valid_local_time
  219
+        @time_zone.period_for_local(@time)
  220
+      rescue ::TZInfo::PeriodNotFound
  221
+        # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
  222
+        @time += 1.hour
  223
+        retry
  224
+      end
  225
+    
  226
+      # Replicating logic from TZInfo::Timezone#utc_to_local because we want to cache the period in an instance variable for reuse
  227
+      def utc_to_local
  228
+        ::TZInfo::TimeOrDateTime.wrap(utc) {|utc| period.to_local(utc)}
  229
+      end
  230
+      
  231
+      # Replicating logic from TZInfo::Timezone#local_to_utc because we want to cache the period in an instance variable for reuse
  232
+      def local_to_utc
  233
+        ::TZInfo::TimeOrDateTime.wrap(time) {|time| period.to_utc(time)}
  234
+      end
217 235
   end
218 236
 end
10  activesupport/lib/active_support/values/time_zone.rb
@@ -183,14 +183,8 @@ def to_s
183 183
     #   Time.zone = "Hawaii"                      # => "Hawaii"
184 184
     #   Time.zone.local(2007, 2, 1, 15, 30, 45)   # => Thu, 01 Feb 2007 15:30:45 HST -10:00
185 185
     def local(*args)
186  
-      t = Time.utc_time(*args)
187  
-      begin
188  
-        result = local_to_utc(t)
189  
-      rescue TZInfo::PeriodNotFound
190  
-        t += 1.hour
191  
-        retry
192  
-      end
193  
-      result.in_time_zone(self)
  186
+      time = Time.utc_time(*args)
  187
+      ActiveSupport::TimeWithZone.new(nil, self, time)
194 188
     end
195 189
     
196 190
     # Returns an ActiveSupport::TimeWithZone instance representing the current time
25  activesupport/test/core_ext/time_with_zone_test.rb
@@ -256,6 +256,31 @@ def test_method_missing_with_non_time_return_value
256 256
       assert_equal 17, twz.sec
257 257
       assert_equal 500, twz.usec
258 258
     end
  259
+    
  260
+    def test_utc_to_local_conversion_saves_period_in_instance_variable
  261
+      assert_nil @twz.instance_variable_get('@period')
  262
+      @twz.time
  263
+      assert_kind_of TZInfo::TimezonePeriod, @twz.instance_variable_get('@period')
  264
+    end
  265
+    
  266
+    def test_instance_created_with_local_time_returns_correct_utc_time
  267
+      twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(1999, 12, 31, 19))
  268
+      assert_equal Time.utc(2000), twz.utc
  269
+    end
  270
+    
  271
+    def test_instance_created_with_local_time_enforces_spring_dst_rules
  272
+      twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,2)) # first second of DST
  273
+      assert_equal Time.utc(2006,4,2,3), twz.time # springs forward to 3AM
  274
+      assert_equal true, twz.dst?
  275
+      assert_equal 'EDT', twz.zone
  276
+    end
  277
+    
  278
+    def test_instance_created_with_local_time_enforces_fall_dst_rules
  279
+      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
  280
+      assert_equal Time.utc(2006,10,29,1), twz.time
  281
+      assert_equal true, twz.dst?
  282
+      assert_equal 'EDT', twz.zone
  283
+    end
259 284
   end
260 285
   
261 286
   class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase

0 notes on commit 77ee522

Please sign in to comment.
Something went wrong with that request. Please try again.