From a54e13bd2e8fb4d6aa0aebe59271699a2d62567b Mon Sep 17 00:00:00 2001 From: Sayan Chakraborty Date: Wed, 28 Jun 2017 19:38:02 +0530 Subject: 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. --- activesupport/lib/active_support/duration.rb | 48 +++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'activesupport/lib') 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 -- cgit v1.2.3