diff options
author | Sayan Chakraborty <mail.sayanc@gmail.com> | 2017-06-28 19:38:02 +0530 |
---|---|---|
committer | Andrew White <andrew.white@unboxed.co> | 2017-07-28 14:06:53 +0100 |
commit | a54e13bd2e8fb4d6aa0aebe59271699a2d62567b (patch) | |
tree | a28949c818ecdb7694faac58e3d61232fa48556f /activesupport/lib | |
parent | c6a28b290ab68455e55bcfddc7dd42edf4909ad8 (diff) | |
download | rails-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.rb | 48 |
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 |