From 32f215c3014e580e512db5e8772d0deadf3d6497 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 29 Nov 2016 13:02:14 -0500 Subject: [PATCH] Treat combined durations as a single unit Prior to this commit, `3.months - 3.months` would result in a duration that has the "parts" of `[[:months, 3], [:months, -3]]`. This would mean that it was subtly different than `2.months - 2.months`. When applied to a time, the date might actually change if the resulting day doesn't exist however many months in the future, even though in both cases we just wanted to add `0`, which should always be an identity operation. With this change, we now store the parts as a hash, so `3.months - 3.months` is simply stored as `{ months: 0 }`. --- activesupport/lib/active_support/duration.rb | 12 +++++++++--- activesupport/test/core_ext/duration_test.rb | 11 +++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 82322291d0..ad2e139ea6 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -15,16 +15,22 @@ module ActiveSupport autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" def initialize(value, parts) #:nodoc: - @value, @parts = value, parts + @value, @parts = value, parts.to_h + @parts.default = 0 end # Adds another Duration or a Numeric to this Duration. Numeric values # are treated as seconds. def +(other) if Duration === other - Duration.new(value + other.value, @parts + other.parts) + parts = @parts.dup + other.parts.each do |(key, value)| + parts[key] += value + end + Duration.new(value + other.value, parts) else - Duration.new(value + other, @parts + [[:seconds, other]]) + seconds = @parts[:seconds] + other + Duration.new(value + other, @parts.merge(seconds: seconds)) end end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index b7b4a9dd00..26a9ea4aee 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -345,6 +345,17 @@ class DurationTest < ActiveSupport::TestCase end end + def test_adding_durations_do_not_hold_prior_states + time = Time.parse("Nov 29, 2016") + # If the implementation adds and subtracts 3 months, the + # resulting date would have been in February so the day will + # change to the 29th. + d1 = 3.months - 3.months + d2 = 2.months - 2.months + + assert_equal time + d1, time + d2 + end + private def eastern_time_zone if Gem.win_platform?