From c69baffdf752e2255fe1efc80ce32017bb1896e7 Mon Sep 17 00:00:00 2001 From: Gordon Chan Date: Fri, 30 May 2014 20:29:30 +1200 Subject: Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost When working with objects with a nanosecond component, the `-` method may unnecessarily cause loss of precision. `ActiveSupport::TimeWithZone#-` should return the same result as if we were using `Time#-`: Time.now.end_of_day - Time.now.beginning_of_day #=> 86399.999999999 Before: Time.zone.now.end_of_day.nsec #=> 999999999 Time.zone.now.end_of_day - Time.zone.now.beginning_of_day #=> 86400.0 After: Time.zone.now.end_of_day - Time.zone.now.beginning_of_day #=> 86399.999999999 --- activesupport/CHANGELOG.md | 20 ++++++++++++++++++++ activesupport/lib/active_support/time_with_zone.rb | 2 +- activesupport/test/core_ext/time_with_zone_test.rb | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) (limited to 'activesupport') diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 6915a491b0..611932874b 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,23 @@ +* Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost + when working with objects with a nanosecond component. + + `ActiveSupport::TimeWithZone#-` should return the same result as if we were + using `Time#-`: + + Time.now.end_of_day - Time.now.beginning_of_day #=> 86399.999999999 + + Before: + + Time.zone.now.end_of_day.nsec #=> 999999999 + Time.zone.now.end_of_day - Time.zone.now.beginning_of_day #=> 86400.0 + + After: + + Time.zone.now.end_of_day - Time.zone.now.beginning_of_day + #=> 86399.999999999 + + *Gordon Chan* + * DateTime `advance` now supports partial days. Before: diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index f2a2f3c3db..d6ffe37833 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -262,7 +262,7 @@ module ActiveSupport # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time, # otherwise move backwards #utc, for accuracy when moving across DST boundaries if other.acts_like?(:time) - utc.to_f - other.to_f + to_time - other.to_time elsif duration_of_variable_length?(other) method_missing(:-, other) else diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 7fe4d4a6b2..6d779bf3d5 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -246,16 +246,31 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1) end + def test_minus_with_time_precision + assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) + assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000)) + end + def test_minus_with_time_with_zone twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] ) twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) assert_equal 86_400.0, twz2 - twz1 end + def test_minus_with_time_with_zone_precision + twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone['UTC'] ) + twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) + assert_equal 86_399.999999998, twz2 - twz1 + end + def test_minus_with_datetime assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) end + def test_minus_with_datetime_precision + assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) + end + def test_minus_with_wrapped_datetime assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1) assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1) -- cgit v1.2.3