mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Fix implicit calculations with scalars and durations
Previously calculations where the scalar is first would be converted to a duration of seconds but this causes issues with dates being converted to times, e.g: Time.zone = "Beijing" # => Asia/Shanghai date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 2 * 1.day # => 172800 seconds date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00 Now the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain the part structure of the duration where possible, e.g: Time.zone = "Beijing" # => Asia/Shanghai date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 2 * 1.day # => 2 days date + 2 * 1.day # => Mon, 22 May 2017 Fixes #29160, #28970.
This commit is contained in:
parent
1c2dd9cb66
commit
28938dd64c
3 changed files with 91 additions and 6 deletions
|
@ -1,3 +1,25 @@
|
|||
* Fix implicit coercion calculations with scalars and durations
|
||||
|
||||
Previously calculations where the scalar is first would be converted to a duration
|
||||
of seconds but this causes issues with dates being converted to times, e.g:
|
||||
|
||||
Time.zone = "Beijing" # => Asia/Shanghai
|
||||
date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
|
||||
2 * 1.day # => 172800 seconds
|
||||
date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00
|
||||
|
||||
Now the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain
|
||||
the part structure of the duration where possible, e.g:
|
||||
|
||||
Time.zone = "Beijing" # => Asia/Shanghai
|
||||
date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
|
||||
2 * 1.day # => 2 days
|
||||
date + 2 * 1.day # => Mon, 22 May 2017
|
||||
|
||||
Fixes #29160, #28970.
|
||||
|
||||
*Andrew White*
|
||||
|
||||
* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving
|
||||
on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version`
|
||||
in Active Record and its use in Action Pack's fragment caching.
|
||||
|
|
|
@ -37,27 +37,56 @@ module ActiveSupport
|
|||
end
|
||||
|
||||
def +(other)
|
||||
calculate(:+, other)
|
||||
if Duration === other
|
||||
seconds = value + other.parts[:seconds]
|
||||
new_parts = other.parts.merge(seconds: seconds)
|
||||
new_value = value + other.value
|
||||
|
||||
Duration.new(new_value, new_parts)
|
||||
else
|
||||
calculate(:+, other)
|
||||
end
|
||||
end
|
||||
|
||||
def -(other)
|
||||
calculate(:-, other)
|
||||
if Duration === other
|
||||
seconds = value - other.parts[:seconds]
|
||||
new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
|
||||
new_parts = new_parts.merge(seconds: seconds)
|
||||
new_value = value - other.value
|
||||
|
||||
Duration.new(new_value, new_parts)
|
||||
else
|
||||
calculate(:-, other)
|
||||
end
|
||||
end
|
||||
|
||||
def *(other)
|
||||
calculate(:*, other)
|
||||
if Duration === other
|
||||
new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
|
||||
new_value = value * other.value
|
||||
|
||||
Duration.new(new_value, new_parts)
|
||||
else
|
||||
calculate(:*, other)
|
||||
end
|
||||
end
|
||||
|
||||
def /(other)
|
||||
calculate(:/, other)
|
||||
if Duration === other
|
||||
new_parts = other.parts.map { |part, other_value| [part, value / other_value] }.to_h
|
||||
new_value = new_parts.inject(0) { |total, (part, value)| total + value * Duration::PARTS_IN_SECONDS[part] }
|
||||
|
||||
Duration.new(new_value, new_parts)
|
||||
else
|
||||
calculate(:/, other)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def calculate(op, other)
|
||||
if Scalar === other
|
||||
Scalar.new(value.public_send(op, other.value))
|
||||
elsif Duration === other
|
||||
Duration.seconds(value).public_send(op, other)
|
||||
elsif Numeric === other
|
||||
Scalar.new(value.public_send(op, other))
|
||||
else
|
||||
|
|
|
@ -337,6 +337,13 @@ class DurationTest < ActiveSupport::TestCase
|
|||
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
|
||||
end
|
||||
|
||||
def test_scalar_plus_parts
|
||||
scalar = ActiveSupport::Duration::Scalar.new(10)
|
||||
|
||||
assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts)
|
||||
assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts)
|
||||
end
|
||||
|
||||
def test_scalar_minus
|
||||
scalar = ActiveSupport::Duration::Scalar.new(10)
|
||||
|
||||
|
@ -349,6 +356,9 @@ class DurationTest < ActiveSupport::TestCase
|
|||
assert_equal 5, scalar - 5.seconds
|
||||
assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
|
||||
|
||||
assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
|
||||
assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
|
||||
|
||||
exception = assert_raises(TypeError) do
|
||||
scalar - "foo"
|
||||
end
|
||||
|
@ -356,6 +366,13 @@ class DurationTest < ActiveSupport::TestCase
|
|||
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
|
||||
end
|
||||
|
||||
def test_scalar_minus_parts
|
||||
scalar = ActiveSupport::Duration::Scalar.new(10)
|
||||
|
||||
assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
|
||||
assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
|
||||
end
|
||||
|
||||
def test_scalar_multiply
|
||||
scalar = ActiveSupport::Duration::Scalar.new(5)
|
||||
|
||||
|
@ -375,6 +392,14 @@ class DurationTest < ActiveSupport::TestCase
|
|||
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
|
||||
end
|
||||
|
||||
def test_scalar_multiply_parts
|
||||
scalar = ActiveSupport::Duration::Scalar.new(1)
|
||||
assert_equal({ days: 2 }, (scalar * 2.days).parts)
|
||||
assert_equal(172800, (scalar * 2.days).value)
|
||||
assert_equal({ days: -2 }, (scalar * -2.days).parts)
|
||||
assert_equal(-172800, (scalar * -2.days).value)
|
||||
end
|
||||
|
||||
def test_scalar_divide
|
||||
scalar = ActiveSupport::Duration::Scalar.new(10)
|
||||
|
||||
|
@ -394,6 +419,15 @@ class DurationTest < ActiveSupport::TestCase
|
|||
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
|
||||
end
|
||||
|
||||
def test_scalar_divide_parts
|
||||
scalar = ActiveSupport::Duration::Scalar.new(10)
|
||||
|
||||
assert_equal({ days: 2 }, (scalar / 5.days).parts)
|
||||
assert_equal(172800, (scalar / 5.days).value)
|
||||
assert_equal({ days: -2 }, (scalar / -5.days).parts)
|
||||
assert_equal(-172800, (scalar / -5.days).value)
|
||||
end
|
||||
|
||||
def test_twelve_months_equals_one_year
|
||||
assert_equal 12.months, 1.year
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue