class ActiveSupport::Duration
1.month.ago # equivalent to Time.now.advance(months: -1)
Time#advance, respectively. It mainly supports the methods on Numeric.
Provides accurate date and time measurements using Date#advance and
def %(other)
Returns the modulo of this Duration by another Duration or Numeric.
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 *(other)
def *(other) if Scalar === other || Duration === other Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?) elsif Numeric === other Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable) else raise_type_error(other) end end
def +(other)
Adds another Duration or a Numeric to this Duration. Numeric values
def +(other) if Duration === other parts = @parts.merge(other._parts) do |_key, value, other_value| value + other_value end Duration.new(value + other.value, parts, @variable || other.variable?) else seconds = @parts.fetch(:seconds, 0) + other Duration.new(value + other, @parts.merge(seconds: seconds), @variable) end end
def +@ # :nodoc:
def +@ # :nodoc: self end
def -(other)
Subtracts another Duration or a Numeric from this Duration. Numeric
def -(other) self + (-other) end
def -@ # :nodoc:
def -@ # :nodoc: Duration.new(-value, @parts.transform_values(&:-@), @variable) end
def /(other)
def /(other) if Scalar === other Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable) elsif Duration === other value / other.value elsif Numeric === other Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable) else raise_type_error(other) end end
def <=>(other)
Compares one Duration with another or a Numeric to this Duration.
def <=>(other) if Duration === other value <=> other.value elsif Numeric === other value <=> other end end
def ==(other)
Returns +true+ if +other+ is also a Duration instance with the
def ==(other) if Duration === other other.value == value else other == value end end
def ===(other) # :nodoc:
def ===(other) # :nodoc: other.is_a?(Duration) rescue ::NoMethodError false end
def _parts # :nodoc:
def _parts # :nodoc: @parts end
def ago(time = ::Time.current)
Calculates a new Time or Date that is as far in the past
def ago(time = ::Time.current) sum(-1, time) end
def as_json(options = nil) # :nodoc:
def as_json(options = nil) # :nodoc: to_i end
def build(value)
ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
to the individual parts:
Creates a new Duration from a seconds value that is converted
def build(value) unless value.is_a?(::Numeric) raise TypeError, "can't build an #{self.name} from a #{value.class.name}" end parts = {} remainder_sign = value <=> 0 remainder = value.round(9).abs variable = false PARTS.each do |part| unless part == :seconds part_in_seconds = PARTS_IN_SECONDS[part] parts[part] = remainder.div(part_in_seconds) * remainder_sign remainder %= part_in_seconds unless parts[part].zero? variable ||= VARIABLE_PARTS.include?(part) end end end unless value == 0 parts[:seconds] = remainder * remainder_sign new(value, parts, variable) end
def calculate_total_seconds(parts)
def calculate_total_seconds(parts) parts.inject(0) do |total, (part, value)| total + value * PARTS_IN_SECONDS[part] end end
def coerce(other) # :nodoc:
def coerce(other) # :nodoc: case other when Scalar [other, self] when Duration [Scalar.new(other.value), self] else [Scalar.new(other), self] end end
def days(value) # :nodoc:
def days(value) # :nodoc: new(value * SECONDS_PER_DAY, { days: value }, true) end
def encode_with(coder) # :nodoc:
def encode_with(coder) # :nodoc: coder.map = { "value" => @value, "parts" => @parts } end
def eql?(other)
Returns +true+ if +other+ is also a Duration instance, which has the
def eql?(other) Duration === other && other.value.eql?(value) end
def hash
def hash @value.hash end
def hours(value) # :nodoc:
def hours(value) # :nodoc: new(value * SECONDS_PER_HOUR, { hours: value }, false) end
def in_days
Returns the amount of days a duration covers as a float
def in_days in_seconds / SECONDS_PER_DAY.to_f end
def in_hours
Returns the amount of hours a duration covers as a float
def in_hours in_seconds / SECONDS_PER_HOUR.to_f end
def in_minutes
Returns the amount of minutes a duration covers as a float
def in_minutes in_seconds / SECONDS_PER_MINUTE.to_f end
def in_months
Returns the amount of months a duration covers as a float
def in_months in_seconds / SECONDS_PER_MONTH.to_f end
def in_weeks
Returns the amount of weeks a duration covers as a float
def in_weeks in_seconds / SECONDS_PER_WEEK.to_f end
def in_years
Returns the amount of years a duration covers as a float
def in_years in_seconds / SECONDS_PER_YEAR.to_f end
def init_with(coder) # :nodoc:
def init_with(coder) # :nodoc: initialize(coder["value"], coder["parts"]) end
def initialize(value, parts, variable = nil) # :nodoc:
def initialize(value, parts, variable = nil) # :nodoc: @value, @parts = value, parts @parts.reject! { |k, v| v.zero? } unless value == 0 @parts.freeze @variable = variable if @variable.nil? @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) } end end
def inspect # :nodoc:
def inspect # :nodoc: return "#{value} seconds" if @parts.empty? @parts. sort_by { |unit, _ | PARTS.index(unit) }. map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }. to_sentence(locale: false) end
def instance_of?(klass) # :nodoc:
def instance_of?(klass) # :nodoc: Duration == klass || value.instance_of?(klass) end
def is_a?(klass) # :nodoc:
def is_a?(klass) # :nodoc: Duration == klass || value.is_a?(klass) end
def iso8601(precision: nil)
Build ISO 8601 Duration string for this duration.
def iso8601(precision: nil) ISO8601Serializer.new(self, precision: precision).serialize end
def method_missing(method, *args, &block)
def method_missing(method, *args, &block) value.public_send(method, *args, &block) end
def minutes(value) # :nodoc:
def minutes(value) # :nodoc: new(value * SECONDS_PER_MINUTE, { minutes: value }, false) end
def months(value) # :nodoc:
def months(value) # :nodoc: new(value * SECONDS_PER_MONTH, { months: value }, true) end
def parse(iso8601duration)
This method allows negative parts to be present in pattern.
See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
Creates a new Duration from string formatted according to ISO 8601 Duration.
def parse(iso8601duration) parts = ISO8601Parser.new(iso8601duration).parse! new(calculate_total_seconds(parts), parts) end
def parts
def parts @parts.dup end
def raise_type_error(other)
def raise_type_error(other) raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" end
def respond_to_missing?(method, _)
def respond_to_missing?(method, _) value.respond_to?(method) end
def seconds(value) # :nodoc:
def seconds(value) # :nodoc: new(value, { seconds: value }, false) end
def since(time = ::Time.current)
Calculates a new Time or Date that is as far in the future
def since(time = ::Time.current) sum(1, time) end
def sum(sign, time = ::Time.current)
def sum(sign, time = ::Time.current) unless time.acts_like?(:time) || time.acts_like?(:date) raise ::ArgumentError, "expected a time or date, got #{time.inspect}" end if @parts.empty? time.since(sign * value) else @parts.inject(time) do |t, (type, number)| if type == :seconds t.since(sign * number) elsif type == :minutes t.since(sign * number * 60) elsif type == :hours t.since(sign * number * 3600) else t.advance(type => sign * number) end end end end
def to_i
Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
In such cases, Ruby's core
1.year.to_i # => 31556952
# equivalent to 365.2425.days.to_i
1.month.to_i # => 2629746
# equivalent to (1.year / 12).to_i
and years are 365.2425 days:
duration of some periods, e.g. months are always 1/12 of year
Note that this conversion makes some assumptions about the
1.day.to_i # => 86400
1.hour.to_i # => 3600
1.minute.to_i # => 60
Returns the number of seconds that this Duration represents.
def to_i @value.to_i end
def to_s
For more information check to_i method.
Returns the amount of seconds a duration covers as a string.
def to_s @value.to_s end
def variable? # :nodoc:
def variable? # :nodoc: @variable end
def weeks(value) # :nodoc:
def weeks(value) # :nodoc: new(value * SECONDS_PER_WEEK, { weeks: value }, true) end
def years(value) # :nodoc:
def years(value) # :nodoc: new(value * SECONDS_PER_YEAR, { years: value }, true) end