aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib
diff options
context:
space:
mode:
authorSayan Chakraborty <mail.sayanc@gmail.com>2017-06-28 19:38:02 +0530
committerAndrew White <andrew.white@unboxed.co>2017-07-28 14:06:53 +0100
commita54e13bd2e8fb4d6aa0aebe59271699a2d62567b (patch)
treea28949c818ecdb7694faac58e3d61232fa48556f /activesupport/lib
parentc6a28b290ab68455e55bcfddc7dd42edf4909ad8 (diff)
downloadrails-a54e13bd2e8fb4d6aa0aebe59271699a2d62567b.tar.gz
rails-a54e13bd2e8fb4d6aa0aebe59271699a2d62567b.tar.bz2
rails-a54e13bd2e8fb4d6aa0aebe59271699a2d62567b.zip
Add missing support for modulo operations on durations
Rails 5.1 introduce an `ActiveSupport::Duration::Scalar` class as a wrapper around a numeric value as a way of ensuring a duration was the outcome of an expression. However the implementation was missing support for modulo operations. This commit adds support for those operations and should result in a duration being returned from expressions involving them. Fixes #29603 and #29743.
Diffstat (limited to 'activesupport/lib')
-rw-r--r--activesupport/lib/active_support/duration.rb48
1 files changed, 47 insertions, 1 deletions
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 0d45566d43..f411bb81df 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -82,6 +82,14 @@ module ActiveSupport
end
end
+ def %(other)
+ if Duration === other
+ Duration.build(value % other.value)
+ else
+ calculate(:%, other)
+ end
+ end
+
private
def calculate(op, other)
if Scalar === other
@@ -115,6 +123,8 @@ module ActiveSupport
years: SECONDS_PER_YEAR
}.freeze
+ PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
+
attr_accessor :value, :parts
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
@@ -165,6 +175,30 @@ module ActiveSupport
new(value * SECONDS_PER_YEAR, [[:years, value]])
end
+ # Creates a new Duration from a seconds value that is converted
+ # to the individual parts:
+ #
+ # ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
+ # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
+ #
+ def build(value)
+ parts = {}
+ remainder = value.to_f
+
+ PARTS.each do |part|
+ unless part == :seconds
+ part_in_seconds = PARTS_IN_SECONDS[part]
+ parts[part] = remainder.div(part_in_seconds)
+ remainder = (remainder % part_in_seconds).round(9)
+ end
+ end
+
+ parts[:seconds] = remainder
+ parts.reject! { |k, v| v.zero? }
+
+ new(value, parts)
+ end
+
private
def calculate_total_seconds(parts)
@@ -242,6 +276,18 @@ module ActiveSupport
end
end
+ # Returns the modulo of this Duration by another Duration or Numeric.
+ # Numeric values are treated as seconds.
+ def %(other)
+ if Duration === other || Scalar === other
+ Duration.build(value % other.value)
+ elsif Numeric === other
+ Duration.build(value % other)
+ else
+ raise_type_error(other)
+ end
+ end
+
def -@ #:nodoc:
Duration.new(-value, parts.map { |type, number| [type, -number] })
end
@@ -326,7 +372,7 @@ module ActiveSupport
def inspect #:nodoc:
parts.
reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
- sort_by { |unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit) }.
+ sort_by { |unit, _ | PARTS.index(unit) }.
map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
to_sentence(locale: ::I18n.default_locale)
end