diff options
Diffstat (limited to 'activesupport/lib/active_support/duration.rb')
-rw-r--r-- | activesupport/lib/active_support/duration.rb | 96 |
1 files changed, 87 insertions, 9 deletions
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index d4424ed792..fe1058762b 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "active_support/core_ext/array/conversions" require "active_support/core_ext/module/delegation" require "active_support/core_ext/object/acts_like" @@ -37,27 +39,61 @@ 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 + value / other.value + else + calculate(:/, other) + 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 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 @@ -87,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" @@ -95,7 +133,7 @@ module ActiveSupport class << self # Creates a new Duration from string formatted according to ISO 8601 Duration. # - # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. # This method allows negative parts to be present in pattern. # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. def parse(iso8601duration) @@ -137,6 +175,29 @@ 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 + + new(value, parts) + end + private def calculate_total_seconds(parts) @@ -149,6 +210,7 @@ module ActiveSupport def initialize(value, parts) #:nodoc: @value, @parts = value, parts.to_h @parts.default = 0 + @parts.reject! { |k, v| v.zero? } end def coerce(other) #:nodoc: @@ -203,8 +265,10 @@ module ActiveSupport # Divides this Duration by a Numeric and returns a new Duration. def /(other) - if Scalar === other || Duration === other + if Scalar === other Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] }) + elsif Duration === other + value / other.value elsif Numeric === other Duration.new(value / other, parts.map { |type, number| [type, number / other] }) else @@ -212,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 @@ -294,9 +370,11 @@ module ActiveSupport alias :before :ago def inspect #:nodoc: + return "0 seconds" if parts.empty? + 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 |