From 53ca22f2e11cd3050d75385bc31b6bb5055a2738 Mon Sep 17 00:00:00 2001
From: Ari Pollak <ajp@aripollak.com>
Date: Tue, 14 Aug 2012 16:33:06 -0400
Subject: 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.
---
 activerecord/CHANGELOG.md                                    | 6 ++++++
 activerecord/lib/active_record/connection_adapters/column.rb | 4 ++--
 activerecord/test/cases/base_test.rb                         | 1 +
 activerecord/test/fixtures/topics.yml                        | 2 +-
 4 files changed, 10 insertions(+), 3 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index faf1bbf232..d0c747d1f2 100644
--- a/activerecord/CHANGELOG.md
+++ b/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*
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index b9045cf1e7..1445bb3b2f 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -208,7 +208,7 @@ module ActiveRecord
           # '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 @@ module ActiveRecord
           # 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
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 062f196a12..04b1d75e3e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -231,6 +231,7 @@ class BasicsTest < ActiveRecord::TestCase
       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
 
diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml
index 93f48aedc4..2b042bd135 100644
--- a/activerecord/test/fixtures/topics.yml
+++ b/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
-- 
cgit v1.2.3