public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are 
now correctly unmarshaled with a utc zone instead of the system local zone [#900 
state:resolved]
jodosha (author)
Wed Aug 27 02:25:20 -0700 2008
gbuesing (committer)
Wed Aug 27 07:12:24 -0700 2008
commit  4d71e99d1fc1f57900288e008d7528c339138cf0
tree    2e29eb4eec3bae3dae3971dbc3c67e8488f01146
parent  e710902f26a2eed7adb22082067df449b9641d00
...
1
2
 
 
3
4
5
...
1
2
3
4
5
6
7
0
@@ -1,5 +1,7 @@
0
 *2.1.1 (next release)*
0
 
0
+* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved]:activesupport/CHANGELOG
0
+
0
 * TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods [Geoff Buesing]
0
 
0
 * Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing]
...
1
2
3
4
5
 
6
7
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
10
11
...
1
2
3
 
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
0
@@ -1,11 +1,32 @@
0
 require 'date'
0
 require 'time'
0
 
0
-# Ruby 1.8-cvs and 1.9 define private Time#to_date
0
 class Time
0
+  # Ruby 1.8-cvs and 1.9 define private Time#to_date
0
   %w(to_date to_datetime).each do |method|
0
     public method if private_instance_methods.include?(method)
0
   end
0
+
0
+  # Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are
0
+  # unmarshaled in the local zone, instead of utc. We're layering behavior on the _dump and _load
0
+  # methods so that utc instances can be flagged on dump, and coerced back to utc on load.
0
+  if RUBY_VERSION < '1.9'
0
+    class << self
0
+      alias_method :_original_load, :_load
0
+      def _load(marshaled_time)
0
+        time = _original_load(marshaled_time)
0
+        utc = time.send(:remove_instance_variable, '@marshal_with_utc_coercion')
0
+        utc ? time.utc : time
0
+      end
0
+    end
0
+    
0
+    alias_method :_original_dump, :_dump
0
+    def _dump(*args)
0
+      obj = self.frozen? ? self.dup : self
0
+      obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
0
+      obj._original_dump(*args)
0
+    end
0
+  end
0
 end
0
 
0
 require 'active_support/core_ext/time/behavior'
...
620
621
622
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
0
@@ -620,3 +620,37 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
0
       old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
0
     end
0
 end
0
+
0
+class TimeExtMarshalingTest < Test::Unit::TestCase
0
+  def test_marshaling_with_utc_instance
0
+    t = Time.utc(2000)
0
+    marshaled = Marshal.dump t
0
+    unmarshaled = Marshal.load marshaled
0
+    assert_equal t, unmarshaled
0
+    assert_equal t.zone, unmarshaled.zone
0
+  end
0
+  
0
+  def test_marshaling_with_local_instance  
0
+    t = Time.local(2000)
0
+    marshaled = Marshal.dump t
0
+    unmarshaled = Marshal.load marshaled
0
+    assert_equal t, unmarshaled
0
+    assert_equal t.zone, unmarshaled.zone
0
+  end
0
+    
0
+  def test_marshaling_with_frozen_utc_instance  
0
+    t = Time.utc(2000).freeze
0
+    marshaled = Marshal.dump t
0
+    unmarshaled = Marshal.load marshaled
0
+    assert_equal t, unmarshaled
0
+    assert_equal t.zone, unmarshaled.zone
0
+  end
0
+  
0
+  def test_marshaling_with_frozen_local_instance  
0
+    t = Time.local(2000).freeze
0
+    marshaled = Marshal.dump t
0
+    unmarshaled = Marshal.load marshaled
0
+    assert_equal t, unmarshaled
0
+    assert_equal t.zone, unmarshaled.zone
0
+  end
0
+end

Comments

han Wed Sep 03 08:51:53 -0700 2008

This breaks in jruby (tested with 1.1.4)

NZKoz Thu Sep 04 05:52:36 -0700 2008

This appears to be a bug in jruby’s unmarshalling code, we’ve reported it upstream and may build our own work around for it.

NZKoz Thu Sep 04 06:15:46 -0700 2008

I’ve shipped a work around for this. It’s still misbehaving in jruby, but the behaviour should at least not cause errors