Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10982: time_zone_attributes_3.diff

File time_zone_attributes_3.diff, 16.1 kB (added by gbuesing, 5 months ago)
  • activesupport/test/time_zone_test.rb

    old new  
    128128    assert TimeZone.us_zones.include?(TimeZone["Hawaii"]) 
    129129    assert !TimeZone.us_zones.include?(TimeZone["Kuala Lumpur"]) 
    130130  end  
     131   
     132  def test_new 
     133    time = TimeZone["Hawaii"].new(2007, 2, 5, 15, 30, 45) 
     134    assert_equal Time.utc(2007, 2, 5, 15, 30, 45), time.time 
     135    assert_equal TimeZone["Hawaii"], time.time_zone 
     136  end 
    131137end 
    132138 
  • activesupport/test/core_ext/time_ext_test.rb

    old new  
    459459    assert_equal  86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), TimeZone['UTC'] ) 
    460460  end 
    461461 
     462  def test_time_created_with_local_constructor_cannot_represent_times_during_hour_skipped_by_dst 
     463    with_env_tz 'US/Eastern' do 
     464      # On Apr 2 2006 at 2:00AM in US, clocks were moved forward to 3:00AM. 
     465      # Therefore, 2AM EST doesn't exist for this date; Time.local fails over to 3:00AM EDT 
     466      assert_equal Time.local(2006, 4, 2, 3), Time.local(2006, 4, 2, 2) 
     467      assert Time.local(2006, 4, 2, 2).dst? 
     468    end 
     469  end 
     470 
    462471  protected 
    463472    def with_env_tz(new_tz = 'US/Eastern') 
    464473      old_tz, ENV['TZ'] = ENV['TZ'], new_tz 
  • activesupport/lib/active_support/values/time_zone.rb

    old new  
    174174  def to_s 
    175175    "(UTC#{formatted_offset}) #{name}" 
    176176  end 
     177   
     178  # Method for creating new ActiveSupport::TimeWithZone instance in time zone of self. Example: 
     179  # 
     180  #   Time.zone = "Hawaii"                    # => "Hawaii" 
     181  #   Time.zone.new(2007, 2, 1, 15, 30, 45)   # => Thu, 01 Feb 2007 15:30:45 HST -10:00 
     182  def new(*args) 
     183    Time.utc_time(*args).change_time_zone(self) 
     184  end 
    177185 
    178186  begin # the following methods depend on the tzinfo gem 
    179187    require_library_or_gem "tzinfo" unless Object.const_defined?(:TZInfo) 
  • activesupport/lib/active_support/core_ext/time/zones.rb

    old new  
    99        end 
    1010         
    1111        module ClassMethods 
     12          attr_accessor :zone_default 
     13           
    1214          def zone 
    13             Thread.current[:time_zone] 
     15            Thread.current[:time_zone] || zone_default 
    1416          end 
    1517 
    1618          # Sets a global default time zone, separate from the system time zone in ENV['TZ'].  
  • activerecord/test/cases/attribute_methods_test.rb

    old new  
    1414    ActiveRecord::Base.attribute_method_suffix *@old_suffixes 
    1515  end 
    1616 
    17  
    1817  def test_match_attribute_method_query_returns_match_data 
    1918    assert_not_nil md = @target.match_attribute_method?('title=') 
    2019    assert_equal 'title', md.pre_match 
     
    9796  def test_only_time_related_columns_are_meant_to_be_cached_by_default 
    9897    expected = %w(datetime timestamp time date).sort 
    9998    assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort 
    100 end 
     99  end 
    101100 
    102101  def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default 
    103102    default_attributes = Topic.cached_attributes 
     
    138137      end 
    139138    end 
    140139  end 
     140   
     141  def test_time_attributes_are_retrieved_in_current_time_zone 
     142    in_time_zone "Pacific Time (US & Canada)" do 
     143      utc_time = Time.utc(2008, 1, 1) 
     144      record   = @target.new 
     145      record[:written_on] = utc_time 
     146      assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time 
     147      assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone 
     148      assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone 
     149      assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly 
     150    end 
     151  end 
     152   
     153  def test_setting_time_zone_aware_attribute_to_utc 
     154    in_time_zone "Pacific Time (US & Canada)" do 
     155      utc_time = Time.utc(2008, 1, 1) 
     156      record   = @target.new 
     157      record.written_on = utc_time 
     158      assert_equal utc_time, record.written_on 
     159      assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone 
     160      assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time 
     161    end 
     162  end 
     163   
     164  def test_setting_time_zone_aware_attribute_in_other_time_zone 
     165    utc_time = Time.utc(2008, 1, 1) 
     166    cst_time = utc_time.in_time_zone("Central Time (US & Canada)") 
     167    in_time_zone "Pacific Time (US & Canada)" do 
     168      record   = @target.new 
     169      record.written_on = cst_time 
     170      assert_equal utc_time, record.written_on 
     171      assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone 
     172      assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time 
     173    end 
     174  end 
     175   
     176  def test_setting_time_zone_aware_attribute_in_current_time_zone 
     177    utc_time = Time.utc(2008, 1, 1) 
     178    in_time_zone "Pacific Time (US & Canada)" do 
     179      record   = @target.new 
     180      record.written_on = utc_time.in_current_time_zone 
     181      assert_equal utc_time, record.written_on 
     182      assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone 
     183      assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time 
     184    end 
     185  end 
    141186 
    142187  private 
    143188  def time_related_columns_on_topic 
    144189    Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) 
    145190  end 
     191   
     192  def in_time_zone(zone) 
     193    old_zone  = Time.zone 
     194    old_tz    = ActiveRecord::Base.time_zone_aware_attributes 
     195 
     196    Time.zone = zone ? TimeZone[zone] : nil 
     197    ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? 
     198    yield 
     199  ensure 
     200    Time.zone = old_zone 
     201    ActiveRecord::Base.time_zone_aware_attributes = old_tz 
     202  end 
    146203end 
  • activerecord/test/cases/base_test.rb

    old new  
    924924    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on 
    925925  end 
    926926 
     927  def test_multiparameter_attributes_on_time_with_utc 
     928    ActiveRecord::Base.default_timezone = :utc 
     929    attributes = { 
     930      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
     931      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" 
     932    } 
     933    topic = Topic.find(1) 
     934    topic.attributes = attributes 
     935    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on 
     936  ensure 
     937    ActiveRecord::Base.default_timezone = :local 
     938  end 
     939 
     940  def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes 
     941    ActiveRecord::Base.time_zone_aware_attributes = true 
     942    ActiveRecord::Base.default_timezone = :utc 
     943    Time.zone = TimeZone[-28800] 
     944    attributes = { 
     945      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
     946      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" 
     947    } 
     948    topic = Topic.find(1) 
     949    topic.attributes = attributes 
     950    assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on 
     951    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time 
     952    assert_equal Time.zone, topic.written_on.time_zone 
     953  ensure 
     954    ActiveRecord::Base.time_zone_aware_attributes = false 
     955    ActiveRecord::Base.default_timezone = :local 
     956    Time.zone = nil 
     957  end 
     958   
     959  def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes 
     960    ActiveRecord::Base.time_zone_aware_attributes = true 
     961    ActiveRecord::Base.default_timezone = :utc 
     962    Time.zone = TimeZone[-28800] 
     963    Topic.skip_time_zone_conversion_for_attributes = [:written_on] 
     964    attributes = { 
     965      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
     966      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00" 
     967    } 
     968    topic = Topic.find(1) 
     969    topic.attributes = attributes 
     970    assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on 
     971    assert_equal false, topic.written_on.respond_to?(:time_zone) 
     972  ensure 
     973    ActiveRecord::Base.time_zone_aware_attributes = false 
     974    ActiveRecord::Base.default_timezone = :local 
     975    Time.zone = nil 
     976    Topic.skip_time_zone_conversion_for_attributes = [] 
     977  end 
     978 
    927979  def test_multiparameter_attributes_on_time_with_empty_seconds 
    928980    attributes = { 
    929981      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
  • activerecord/lib/active_record/attribute_methods.rb

    old new  
    88      base.attribute_method_suffix(*DEFAULT_SUFFIXES) 
    99      base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false 
    1010      base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT 
     11      base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false 
     12      base.time_zone_aware_attributes = false 
     13      base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false 
     14      base.skip_time_zone_conversion_for_attributes = [] 
    1115    end 
    1216 
    1317    # Declare and check for suffixed attribute methods. 
     
    6468          unless instance_method_already_implemented?(name) 
    6569            if self.serialized_attributes[name] 
    6670              define_read_method_for_serialized_attribute(name) 
     71            elsif create_time_zone_conversion_attribute?(name, column) 
     72              define_read_method_for_time_zone_conversion(name) 
    6773            else 
    6874              define_read_method(name.to_sym, name, column) 
    6975            end 
    7076          end 
    7177 
    7278          unless instance_method_already_implemented?("#{name}=") 
    73             define_write_method(name.to_sym) 
     79            if create_time_zone_conversion_attribute?(name, column) 
     80              define_write_method_for_time_zone_conversion(name) 
     81            else   
     82              define_write_method(name.to_sym) 
     83            end 
    7484          end 
    7585 
    7686          unless instance_method_already_implemented?("#{name}?") 
     
    121131          @@attribute_method_suffixes ||= [] 
    122132        end 
    123133         
     134        def create_time_zone_conversion_attribute?(name, column) 
     135          time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) 
     136        end 
     137         
    124138        # Define an attribute reader method.  Cope with nil column. 
    125139        def define_read_method(symbol, attr_name, column) 
    126140          cast_code = column.type_cast_code('v') if column 
     
    140154        def define_read_method_for_serialized_attribute(attr_name) 
    141155          evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end" 
    142156        end 
     157         
     158        def define_read_method_for_time_zone_conversion(attr_name) 
     159          method_body = <<-EOV 
     160            def #{attr_name}(reload = false) 
     161              cached = @attributes_cache['#{attr_name}'] 
     162              return cached if cached && !reload 
     163              time = read_attribute('#{attr_name}') 
     164              @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_current_time_zone : time 
     165            end 
     166          EOV 
     167          evaluate_attribute_method attr_name, method_body 
     168        end 
    143169 
    144170        # Define an attribute ? method. 
    145171        def define_question_method(attr_name) 
     
    149175        def define_write_method(attr_name) 
    150176          evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}=" 
    151177        end 
     178         
     179        def define_write_method_for_time_zone_conversion(attr_name) 
     180          method_body = <<-EOV 
     181            def #{attr_name}=(time) 
     182              if time 
     183                time = time.to_time rescue time unless time.acts_like?(:time) 
     184                time = time.in_current_time_zone if time.acts_like?(:time) 
     185              end 
     186              write_attribute(:#{attr_name}, time) 
     187            end 
     188          EOV 
     189          evaluate_attribute_method attr_name, method_body, "#{attr_name}=" 
     190        end 
    152191 
    153192        # Evaluate the definition for an attribute related method 
    154193        def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name) 
     
    303342      end 
    304343      super 
    305344    end 
    306      
    307345 
    308346    private 
    309347     
  • activerecord/lib/active_record/base.rb

    old new  
    24702470      end 
    24712471 
    24722472      # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself. 
    2473       def instantiate_time_object(*values) 
    2474         @@default_timezone == :utc ? Time.utc(*values) : Time.local(*values) 
     2473      def instantiate_time_object(name, values) 
     2474        if Time.zone && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym) 
     2475          Time.zone.new(*values) 
     2476        else 
     2477          @@default_timezone == :utc ? Time.utc(*values) : Time.local(*values) 
     2478        end 
    24752479      end 
    24762480 
    24772481      def execute_callstack_for_multiparameter_attributes(callstack) 
     
    24832487          else 
    24842488            begin 
    24852489              value = if Time == klass 
    2486                 instantiate_time_object(*values) 
     2490                instantiate_time_object(name, values) 
    24872491              elsif Date == klass 
    24882492                begin 
    24892493                  Date.new(*values) 
    24902494                rescue ArgumentError => ex # if Date.new raises an exception on an invalid date 
    2491                   instantiate_time_object(*values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates 
     2495                  instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates 
    24922496                end 
    24932497              else 
    24942498                klass.new(*values) 
  • railties/lib/initializer.rb

    old new  
    8181      initialize_dependency_mechanism 
    8282      initialize_whiny_nils 
    8383      initialize_temporary_session_directory 
     84      initialize_time_zone 
    8485      initialize_framework_settings 
    8586 
    8687      add_support_load_paths 
     
    316317      end 
    317318    end 
    318319 
     320    def initialize_time_zone 
     321      if configuration.time_zone 
     322        Time.zone_default = TimeZone[configuration.time_zone] 
     323        if configuration.frameworks.include?(:active_record) 
     324          ActiveRecord::Base.time_zone_aware_attributes = true 
     325          ActiveRecord::Base.default_timezone = :utc 
     326        end 
     327      end 
     328    end 
     329 
    319330    # Initializes framework-specific settings for each of the loaded frameworks 
    320331    # (Configuration#frameworks). The available settings map to the accessors 
    321332    # on each of the corresponding Base classes. 
     
    456467    end 
    457468    alias_method :breakpoint_server=, :breakpoint_server 
    458469 
     470    # Sets the default time_zone.  Setting this will enable time_zone 
     471    # awareness for ActiveRecord models and set the ActiveRecord default 
     472    # timezone to :utc. 
     473    attr_accessor :time_zone 
     474 
    459475    # Create a new Configuration instance, initialized with the default 
    460476    # values. 
    461477    def initialize