On Jun 20, 2012, at 1:32 PM, cschulte22 wrote:

> What version of rails?  Can you post a few lines of the code?  The way that 
> you're setting your timestamp field may be causing the problem...

3.1.3

I’m not doing anything particularly clever. I’m creating a record based on 
params. I don’t explicitly set the timestamp, so it’s just being set by Rails 
automatically.

I can see in the database that the timestamp is set to local time.

I read all over the place how Rails will stores all values in UTC, but this 
just doesn’t appear to be true. The relevant logic is in 
ActiveRecord::ConnectionAdapters::Quoting:

      def quoted_date(value)
        if value.acts_like?(:time)
          zone_conversion_method = ActiveRecord::Base.default_timezone == :utc 
? :getutc : :getlocal
          value.respond_to?(zone_conversion_method) ? 
value.send(zone_conversion_method) : value
        else
          value
        end.to_s(:db)
      end

On the third line of the above, my ActiveRecord::Base.default_timezone is not 
:utc, so it does :get local. It’s writing the local time to the column.

The corresponding logic when reading a timestamp is in 
ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods::define_method_attribute:

          def define_method_attribute(attr_name)
            if create_time_zone_conversion_attribute?(attr_name, 
columns_hash[attr_name])
              method_body, line = <<-EOV, __LINE__ + 1
                def _#{attr_name}
                  cached = @attributes_cache['#{attr_name}']
                  return cached if cached
                  time = _read_attribute('#{attr_name}')
                  @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? 
time.in_time_zone : time
                end
                alias #{attr_name} _#{attr_name}
              EOV
              generated_attribute_methods.module_eval(method_body, __FILE__, 
line)
            else
              super
            end
          end

this calls ActiveRecord::AttributeMethods::Read::ClassMethods:: _read_attribute:

      def _read_attribute(attr_name)
        attr_name = attr_name.to_s
        attr_name = self.class.primary_key if attr_name == 'id'
        value = @attributes[attr_name]
        unless value.nil?
          if column = column_for_attribute(attr_name)
            if unserializable_attribute?(attr_name, column)
              unserialize_attribute(attr_name)
            else
              column.type_cast(value)
            end
          else
            value
          end
        end
      end

which trampolines down to fast_string_to_time:

          # Doesn't handle time zones.
          def fast_string_to_time(string)
            if string =~ Format::ISO_DATETIME
              microsec = ($7.to_f * 1_000_000).to_i
              new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, 
microsec
            end
          end

which gets back to define_method_attribute, which then does:

                  @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? 
time.in_time_zone : time

this pulls the default time zone (Pacific) and converts my value to Pacific. 
But the value in the database is already in Pacific time, so I’m screwed.

Is it just me, or is there a bug here? The thing that bothers me is that this 
seems like a pretty major issue not to have been reported and fixed before now.

-- 
SD Ruby mailing list
[email protected]
http://groups.google.com/group/sdruby

Reply via email to