Class: ActiveSupport::Duration

Inherits:
Object
  • Object
show all
Defined in:
activesupport/lib/active_support/duration.rb,
activesupport/lib/active_support/duration/iso8601_parser.rb,
activesupport/lib/active_support/duration/iso8601_serializer.rb

Overview

Active Support Duration

Provides accurate date and time measurements using Date#advance and Time#advance, respectively. It mainly supports the methods on Numeric.

1.month.ago       # equivalent to Time.now.advance(months: -1)

Defined Under Namespace

Classes: ISO8601Parser, ISO8601Serializer, Scalar

Constant Summary collapse

SECONDS_PER_MINUTE =
60
SECONDS_PER_HOUR =
3600
SECONDS_PER_DAY =
86400
SECONDS_PER_WEEK =
604800
SECONDS_PER_MONTH =

1/12 of a gregorian year

2629746
SECONDS_PER_YEAR =

length of a gregorian year (365.2425 days)

31556952
PARTS_IN_SECONDS =
{
  seconds: 1,
  minutes: SECONDS_PER_MINUTE,
  hours:   SECONDS_PER_HOUR,
  days:    SECONDS_PER_DAY,
  weeks:   SECONDS_PER_WEEK,
  months:  SECONDS_PER_MONTH,
  years:   SECONDS_PER_YEAR
}.freeze
PARTS =
[:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
VARIABLE_PARTS =
[:years, :months, :weeks, :days].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, parts, variable = nil) ⇒ Duration

:nodoc:



226
227
228
229
230
231
232
233
234
235
# File 'activesupport/lib/active_support/duration.rb', line 226

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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missingObject (private)



516
517
518
# File 'activesupport/lib/active_support/duration.rb', line 516

def method_missing(...)
  value.public_send(...)
end

Instance Attribute Details

#valueObject (readonly)

Returns the value of attribute value.



133
134
135
# File 'activesupport/lib/active_support/duration.rb', line 133

def value
  @value
end

Class Method Details

.===(other) ⇒ Object

:nodoc:



149
150
151
152
153
# File 'activesupport/lib/active_support/duration.rb', line 149

def ===(other) # :nodoc:
  other.is_a?(Duration)
rescue ::NoMethodError
  false
end

.build(value) ⇒ Object

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}


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'activesupport/lib/active_support/duration.rb', line 189

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

.days(value) ⇒ Object

:nodoc:



167
168
169
# File 'activesupport/lib/active_support/duration.rb', line 167

def days(value) # :nodoc:
  new(value * SECONDS_PER_DAY, { days: value }, true)
end

.hours(value) ⇒ Object

:nodoc:



163
164
165
# File 'activesupport/lib/active_support/duration.rb', line 163

def hours(value) # :nodoc:
  new(value * SECONDS_PER_HOUR, { hours: value }, false)
end

.minutes(value) ⇒ Object

:nodoc:



159
160
161
# File 'activesupport/lib/active_support/duration.rb', line 159

def minutes(value) # :nodoc:
  new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
end

.months(value) ⇒ Object

:nodoc:



175
176
177
# File 'activesupport/lib/active_support/duration.rb', line 175

def months(value) # :nodoc:
  new(value * SECONDS_PER_MONTH, { months: value }, true)
end

.parse(iso8601duration) ⇒ Object

Creates a new Duration from string formatted according to ISO 8601 Duration.

See 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.



144
145
146
147
# File 'activesupport/lib/active_support/duration.rb', line 144

def parse(iso8601duration)
  parts = ISO8601Parser.new(iso8601duration).parse!
  new(calculate_total_seconds(parts), parts)
end

.seconds(value) ⇒ Object

:nodoc:



155
156
157
# File 'activesupport/lib/active_support/duration.rb', line 155

def seconds(value) # :nodoc:
  new(value, { seconds: value }, false)
end

.weeks(value) ⇒ Object

:nodoc:



171
172
173
# File 'activesupport/lib/active_support/duration.rb', line 171

def weeks(value) # :nodoc:
  new(value * SECONDS_PER_WEEK, { weeks: value }, true)
end

.years(value) ⇒ Object

:nodoc:



179
180
181
# File 'activesupport/lib/active_support/duration.rb', line 179

def years(value) # :nodoc:
  new(value * SECONDS_PER_YEAR, { years: value }, true)
end

Instance Method Details

#%(other) ⇒ Object

Returns the modulo of this Duration by another Duration or Numeric. Numeric values are treated as seconds.



312
313
314
315
316
317
318
319
320
# File 'activesupport/lib/active_support/duration.rb', line 312

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

#*(other) ⇒ Object

Multiplies this Duration by a Numeric and returns a new Duration.



287
288
289
290
291
292
293
294
295
# File 'activesupport/lib/active_support/duration.rb', line 287

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

#+(other) ⇒ Object

Adds another Duration or a Numeric to this Duration. Numeric values are treated as seconds.



268
269
270
271
272
273
274
275
276
277
278
# File 'activesupport/lib/active_support/duration.rb', line 268

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

#+@Object

:nodoc:



326
327
328
# File 'activesupport/lib/active_support/duration.rb', line 326

def +@ # :nodoc:
  self
end

#-(other) ⇒ Object

Subtracts another Duration or a Numeric from this Duration. Numeric values are treated as seconds.



282
283
284
# File 'activesupport/lib/active_support/duration.rb', line 282

def -(other)
  self + (-other)
end

#-@Object

:nodoc:



322
323
324
# File 'activesupport/lib/active_support/duration.rb', line 322

def -@ # :nodoc:
  Duration.new(-value, @parts.transform_values(&:-@), @variable)
end

#/(other) ⇒ Object

Divides this Duration by a Numeric and returns a new Duration.



298
299
300
301
302
303
304
305
306
307
308
# File 'activesupport/lib/active_support/duration.rb', line 298

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

#<=>(other) ⇒ Object

Compares one Duration with another or a Numeric to this Duration. Numeric values are treated as seconds.



258
259
260
261
262
263
264
# File 'activesupport/lib/active_support/duration.rb', line 258

def <=>(other)
  if Duration === other
    value <=> other.value
  elsif Numeric === other
    value <=> other
  end
end

#==(other) ⇒ Object

Returns true if other is also a Duration instance with the same value, or if other == value.



341
342
343
344
345
346
347
# File 'activesupport/lib/active_support/duration.rb', line 341

def ==(other)
  if Duration === other
    other.value == value
  else
    other == value
  end
end

#_partsObject

:nodoc:



481
482
483
# File 'activesupport/lib/active_support/duration.rb', line 481

def _parts # :nodoc:
  @parts
end

#ago(time = ::Time.current) ⇒ Object Also known as: until, before

Calculates a new Time or Date that is as far in the past as this Duration represents.



444
445
446
# File 'activesupport/lib/active_support/duration.rb', line 444

def ago(time = ::Time.current)
  sum(-1, time)
end

#as_json(options = nil) ⇒ Object

:nodoc:



459
460
461
# File 'activesupport/lib/active_support/duration.rb', line 459

def as_json(options = nil) # :nodoc:
  to_i
end

#coerce(other) ⇒ Object

:nodoc:



245
246
247
248
249
250
251
252
253
254
# File 'activesupport/lib/active_support/duration.rb', line 245

def coerce(other) # :nodoc:
  case other
  when Scalar
    [other, self]
  when Duration
    [Scalar.new(other.value), self]
  else
    [Scalar.new(other), self]
  end
end

#encode_with(coder) ⇒ Object

:nodoc:



467
468
469
# File 'activesupport/lib/active_support/duration.rb', line 467

def encode_with(coder) # :nodoc:
  coder.map = { "value" => @value, "parts" => @parts }
end

#eql?(other) ⇒ Boolean

Returns true if other is also a Duration instance, which has the same parts as this one.

Returns:

  • (Boolean)


426
427
428
# File 'activesupport/lib/active_support/duration.rb', line 426

def eql?(other)
  Duration === other && other.value.eql?(value)
end

#hashObject



430
431
432
# File 'activesupport/lib/active_support/duration.rb', line 430

def hash
  @value.hash
end

#in_daysObject

Returns the amount of days a duration covers as a float

12.hours.in_days # => 0.5


399
400
401
# File 'activesupport/lib/active_support/duration.rb', line 399

def in_days
  in_seconds / SECONDS_PER_DAY.to_f
end

#in_hoursObject

Returns the amount of hours a duration covers as a float

1.day.in_hours # => 24.0


392
393
394
# File 'activesupport/lib/active_support/duration.rb', line 392

def in_hours
  in_seconds / SECONDS_PER_HOUR.to_f
end

#in_minutesObject

Returns the amount of minutes a duration covers as a float

1.day.in_minutes # => 1440.0


385
386
387
# File 'activesupport/lib/active_support/duration.rb', line 385

def in_minutes
  in_seconds / SECONDS_PER_MINUTE.to_f
end

#in_monthsObject

Returns the amount of months a duration covers as a float

9.weeks.in_months # => 2.07


413
414
415
# File 'activesupport/lib/active_support/duration.rb', line 413

def in_months
  in_seconds / SECONDS_PER_MONTH.to_f
end

#in_weeksObject

Returns the amount of weeks a duration covers as a float

2.months.in_weeks # => 8.696


406
407
408
# File 'activesupport/lib/active_support/duration.rb', line 406

def in_weeks
  in_seconds / SECONDS_PER_WEEK.to_f
end

#in_yearsObject

Returns the amount of years a duration covers as a float

30.days.in_years # => 0.082


420
421
422
# File 'activesupport/lib/active_support/duration.rb', line 420

def in_years
  in_seconds / SECONDS_PER_YEAR.to_f
end

#init_with(coder) ⇒ Object

:nodoc:



463
464
465
# File 'activesupport/lib/active_support/duration.rb', line 463

def init_with(coder) # :nodoc:
  initialize(coder["value"], coder["parts"])
end

#inspectObject

:nodoc:



450
451
452
453
454
455
456
457
# File 'activesupport/lib/active_support/duration.rb', line 450

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

#instance_of?(klass) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)


335
336
337
# File 'activesupport/lib/active_support/duration.rb', line 335

def instance_of?(klass) # :nodoc:
  Duration == klass || value.instance_of?(klass)
end

#is_a?(klass) ⇒ Boolean Also known as: kind_of?

:nodoc:

Returns:

  • (Boolean)


330
331
332
# File 'activesupport/lib/active_support/duration.rb', line 330

def is_a?(klass) # :nodoc:
  Duration == klass || value.is_a?(klass)
end

#iso8601(precision: nil) ⇒ Object

Build ISO 8601 Duration string for this duration. The precision parameter can be used to limit seconds' precision of duration.



473
474
475
# File 'activesupport/lib/active_support/duration.rb', line 473

def iso8601(precision: nil)
  ISO8601Serializer.new(self, precision: precision).serialize
end

#partsObject

Returns a copy of the parts hash that defines the duration.

5.minutes.parts # => {:minutes=>5}
3.years.parts # => {:years=>3}


241
242
243
# File 'activesupport/lib/active_support/duration.rb', line 241

def parts
  @parts.dup
end

#since(time = ::Time.current) ⇒ Object Also known as: from_now, after

Calculates a new Time or Date that is as far in the future as this Duration represents.



436
437
438
# File 'activesupport/lib/active_support/duration.rb', line 436

def since(time = ::Time.current)
  sum(1, time)
end

#to_iObject Also known as: in_seconds

Returns the number of seconds that this Duration represents.

1.minute.to_i   # => 60
1.hour.to_i     # => 3600
1.day.to_i      # => 86400

Note that this conversion makes some assumptions about the duration of some periods, e.g. months are always 1/12 of year and years are 365.2425 days:

equivalent to (1.year / 12).to_i

1.month.to_i # => 2629746

equivalent to 365.2425.days.to_i

1.year.to_i # => 31556952

In such cases, Ruby's core Date and Time should be used for precision date and time arithmetic.



377
378
379
# File 'activesupport/lib/active_support/duration.rb', line 377

def to_i
  @value.to_i
end

#to_sObject

Returns the amount of seconds a duration covers as a string. For more information check to_i method.

1.day.to_s # => "86400"


353
354
355
# File 'activesupport/lib/active_support/duration.rb', line 353

def to_s
  @value.to_s
end

#variable?Boolean

:nodoc:

Returns:

  • (Boolean)


477
478
479
# File 'activesupport/lib/active_support/duration.rb', line 477

def variable? # :nodoc:
  @variable
end