Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix occasional microsecond conversion inaccuracy #7352

Merged
merged 1 commit into from

7 participants

@aripollak

This patch was originally submitted by Logan Bowers in https://rails.lighthouseapp.com/projects/8994/tickets/4498-rails-loses-precision-when-deserializing-timestamps-from-postgresql, but I've shortened the test case significantly and I can reliably reproduce it.

In psql, run this:

CREATE TABLE tmps (id serial primary key, atime timestamp with time zone );

Then open up rails console and run this:

class Tmp < ActiveRecord::Base; end

time = Time.at(1344803062, 129346).utc
t = Tmp.create!({atime: time}, without_protection: true)
puts "Original time: " + time.strftime('%s.%N')
puts "Retrieved time: " + t.reload.atime.strftime('%s.%N')
puts "Raw time from DB: " + t.atime_before_type_cast

I get the following results without the patch:

1.9.3p194 :005 > class Tmp < ActiveRecord::Base; end
1.9.3p194 :007 >   time = Time.at(1344803062, 129346).utc
 => 2012-08-12 20:24:22 UTC 
1.9.3p194 :008 > t = Tmp.create!({atime: time}, without_protection: true)
   (0.1ms)  BEGIN
  SQL (0.8ms)  INSERT INTO "tmps" ("atime") VALUES ('2012-08-12 20:24:22.129346') RETURNING "id"
   (2.6ms)  COMMIT
 => #<Tmp id: 3, atime: "2012-08-12 20:24:22"> 
1.9.3p194 :009 > puts "Original time: " + time.strftime('%s.%N')
Original time: 1344803062.129346000
1.9.3p194 :010 > puts "Retrieved time: " + t.reload.atime.strftime('%s.%N')
Retrieved time: 1344788662.129345000
1.9.3p194 :011 > puts "Raw time from DB: " + t.atime_before_type_cast
Raw time from DB: 2012-08-12 20:24:22.129346+00

Note that the DB says there are .129346 seconds, but Ruby says it's .129345 instead. With the patch, the times are all in sync.
This diff should also apply cleanly to 3.2.

CC: @tenderlove, @rafaelfranca

@sikachu
Collaborator

/cc @tenderlove @jonleighton. Can you guys review this patch?

@jonleighton jonleighton was assigned
@aripollak

Also, in case it wasn't obvious, the ramifications of this bug are rather important:

1.9.3-p194 :006 > time == t.atime
 => false 
1.9.3-p194 :011 > Tmp.where('atime <= ?', t.atime)
 => [] 
@steveklabnik
Collaborator

This already needs a rebase :/

@aripollak aripollak Fix occasional microsecond conversion inaccuracy
ActiveRecord::ConnectionAdapters::Column#microseconds did an unnecessary
conversion to from Rational to float when calculating the integer number
of microseconds. Some terminating decimal numbers in base10 are
repeating decimal numbers in base2 (the format of float), and
occasionally this causes a rounding error.
Patch & explanation originally from Logan Bowers.
53ca22f
@aripollak

Rebased. If it's easier, I can just rip out the changelog modification.

@rafaelfranca

Seems great!

@aripollak do you think this is related with #6986, #6975 and #7119?

@aripollak
@rafaelfranca

Right! Thank you for the explanation.

@rafaelfranca rafaelfranca merged commit 8f4ee48 into rails:master
@rochefort

Possibly, is this not fast_string_to_date but fast_string_to_time?

You're right, fixed in d23c761, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 15, 2012
  1. @aripollak

    Fix occasional microsecond conversion inaccuracy

    aripollak authored
    ActiveRecord::ConnectionAdapters::Column#microseconds did an unnecessary
    conversion to from Rational to float when calculating the integer number
    of microseconds. Some terminating decimal numbers in base10 are
    repeating decimal numbers in base2 (the format of float), and
    occasionally this causes a rounding error.
    Patch & explanation originally from Logan Bowers.
This page is out of date. Refresh to see the latest.
View
6 activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Fix Column.microseconds and Column.fast_string_to_date to avoid converting
+ timestamp seconds to a float, since it occasionally results in inaccuracies
+ with microsecond-precision times. Fixes #7352.
+
+ *Ari Pollak*
+
* Raise `ArgumentError` if list of attributes to change is empty in `update_all`.
*Roman Shatsov*
View
4 activerecord/lib/active_record/connection_adapters/column.rb
@@ -208,7 +208,7 @@ def value_to_decimal(value)
# '0.123456' -> 123456
# '1.123456' -> 123456
def microseconds(time)
- ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
end
def new_date(year, mon, mday)
@@ -233,7 +233,7 @@ def fast_string_to_date(string)
# 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
+ microsec = ($7.to_r * 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
View
1  activerecord/test/cases/base_test.rb
@@ -231,6 +231,7 @@ def test_preserving_time_objects
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
+ assert_equal 129346, Topic.find(3).written_on.usec
end
end
View
2  activerecord/test/fixtures/topics.yml
@@ -25,7 +25,7 @@ third:
id: 3
title: The Third Topic of the day
author_name: Carl
- written_on: 2005-07-15t15:28:00.0099+01:00
+ written_on: 2012-08-12t20:24:22.129346+00:00
content: I'm a troll
approved: true
replies_count: 1
Something went wrong with that request. Please try again.