Class: Philiprehberger::Duration

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/philiprehberger/duration.rb,
lib/philiprehberger/duration/parser.rb,
lib/philiprehberger/duration/version.rb,
lib/philiprehberger/duration/formatter.rb

Overview

Immutable Duration value object

Defined Under Namespace

Modules: Formatter, Parser Classes: Error

Constant Summary collapse

VERSION =
'0.6.0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(total_seconds) ⇒ Duration

Returns a new instance of Duration.

Raises:



73
74
75
76
77
78
79
# File 'lib/philiprehberger/duration.rb', line 73

def initialize(total_seconds)
  raise Error, 'Seconds must be numeric' unless total_seconds.is_a?(Numeric)
  raise Error, 'Seconds must not be negative' if total_seconds.negative?

  @total_seconds = total_seconds.to_f
  freeze
end

Instance Attribute Details

#total_secondsObject (readonly)

Returns the value of attribute total_seconds.



14
15
16
# File 'lib/philiprehberger/duration.rb', line 14

def total_seconds
  @total_seconds
end

Class Method Details

.between(time_a, time_b) ⇒ Duration

Calculate the duration between two Time objects

Parameters:

  • time_a (Time)

    start time

  • time_b (Time)

    end time

Returns:



69
70
71
# File 'lib/philiprehberger/duration.rb', line 69

def self.between(time_a, time_b)
  new((time_b - time_a).abs)
end

.from_hash(weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0) ⇒ Duration

Construct a duration from named components

Parameters:

  • weeks (Numeric) (defaults to: 0)

    number of weeks

  • days (Numeric) (defaults to: 0)

    number of days

  • hours (Numeric) (defaults to: 0)

    number of hours

  • minutes (Numeric) (defaults to: 0)

    number of minutes

  • seconds (Numeric) (defaults to: 0)

    number of seconds

Returns:



52
53
54
55
# File 'lib/philiprehberger/duration.rb', line 52

def self.from_hash(weeks: 0, days: 0, hours: 0, minutes: 0, seconds: 0)
  total = (weeks * 604_800) + (days * 86_400) + (hours * 3600) + (minutes * 60) + seconds
  new(total)
end

.parse(input) ⇒ Duration

Parse a duration from a string or numeric value

Parameters:

  • input (String, Numeric)

    human string, ISO 8601, or seconds

Returns:

Raises:

  • (Error)

    if input cannot be parsed



21
22
23
24
# File 'lib/philiprehberger/duration.rb', line 21

def self.parse(input)
  seconds = Parser.parse(input)
  new(seconds)
end

.parse?(input) ⇒ Duration?

Non-raising variant of ‘.parse`

Returns ‘nil` when input is `nil`, empty, or cannot be parsed. Returns a `Duration` otherwise.

rubocop:disable Style/ReturnNilInPredicateMethodDefinition

Parameters:

  • input (String, Numeric, nil)

    human string, ISO 8601, or seconds

Returns:



34
35
36
37
38
39
40
41
# File 'lib/philiprehberger/duration.rb', line 34

def self.parse?(input)
  return nil if input.nil?
  return nil if input.respond_to?(:empty?) && input.empty?

  parse(input)
rescue Error, ArgumentError
  nil
end

.zeroDuration

Return a zero-length duration

Returns:



60
61
62
# File 'lib/philiprehberger/duration.rb', line 60

def self.zero
  new(0)
end

Instance Method Details

#*(other) ⇒ Object



177
178
179
# File 'lib/philiprehberger/duration.rb', line 177

def *(other)
  self.class.new(@total_seconds * other)
end

#+(other) ⇒ Object



169
170
171
# File 'lib/philiprehberger/duration.rb', line 169

def +(other)
  self.class.new(@total_seconds + resolve_seconds(other))
end

#-(other) ⇒ Object



173
174
175
# File 'lib/philiprehberger/duration.rb', line 173

def -(other)
  self.class.new(@total_seconds - resolve_seconds(other))
end

#/(other) ⇒ Object



181
182
183
# File 'lib/philiprehberger/duration.rb', line 181

def /(other)
  self.class.new(@total_seconds / other)
end

#<=>(other) ⇒ Object



185
186
187
188
189
# File 'lib/philiprehberger/duration.rb', line 185

def <=>(other)
  return nil unless other.is_a?(Duration)

  @total_seconds <=> other.total_seconds
end

#daysInteger

Extracted day component (0-6)

Returns:

  • (Integer)


126
127
128
# File 'lib/philiprehberger/duration.rb', line 126

def days
  (@total_seconds.to_i % 604_800) / 86_400
end

#format(pattern) ⇒ String

Format using strftime-style tokens: %W (weeks), %D (days), %H (hours, zero-padded), %M (minutes, zero-padded), %S (seconds, zero-padded), %T (H:M:S), %s (total seconds as integer), %%

Parameters:

  • pattern (String)

Returns:

  • (String)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/philiprehberger/duration.rb', line 93

def format(pattern)
  pattern.gsub(/%[WDHMSTs%]/) do |token|
    case token
    when '%W' then weeks.to_s
    when '%D' then days.to_s
    when '%H' then hours.to_s.rjust(2, '0')
    when '%M' then minutes.to_s.rjust(2, '0')
    when '%S' then seconds.to_s.rjust(2, '0')
    when '%T' then "#{hours.to_s.rjust(2, '0')}:#{minutes.to_s.rjust(2, '0')}:#{seconds.to_s.rjust(2, '0')}"
    when '%s' then @total_seconds.to_i.to_s
    when '%%' then '%'
    end
  end
end

#hoursInteger

Extracted hour component (0-23)

Returns:

  • (Integer)


132
133
134
# File 'lib/philiprehberger/duration.rb', line 132

def hours
  (@total_seconds.to_i % 86_400) / 3600
end

#inspectObject



192
# File 'lib/philiprehberger/duration.rb', line 192

def inspect = "#<Duration #{to_human}>"

#minutesInteger

Extracted minute component (0-59)

Returns:

  • (Integer)


138
139
140
# File 'lib/philiprehberger/duration.rb', line 138

def minutes
  (@total_seconds.to_i % 3600) / 60
end

#round(unit) ⇒ Duration

Round to the nearest unit

Parameters:

  • unit (Symbol)

    :week, :day, :hour, :minute, or :second

Returns:



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/philiprehberger/duration.rb', line 157

def round(unit)
  rounded = case unit
            when :week then ((@total_seconds / 604_800.0).round * 604_800)
            when :day then ((@total_seconds / 86_400.0).round * 86_400)
            when :hour then ((@total_seconds / 3600.0).round * 3600)
            when :minute then ((@total_seconds / 60.0).round * 60)
            when :second then @total_seconds.round
            else raise Error, "Unknown unit: #{unit}"
            end
  self.class.new(rounded)
end

#secondsInteger

Extracted second component (0-59)

Returns:

  • (Integer)


144
145
146
# File 'lib/philiprehberger/duration.rb', line 144

def seconds
  @total_seconds.to_i % 60
end

#to_daysObject



111
# File 'lib/philiprehberger/duration.rb', line 111

def to_days = @total_seconds / 86_400.0

#to_fObject



114
# File 'lib/philiprehberger/duration.rb', line 114

def to_f = @total_seconds

#to_hashHash

Return components as a hash

Returns:

  • (Hash)

    { weeks:, days:, hours:, minutes:, seconds: }



150
151
152
# File 'lib/philiprehberger/duration.rb', line 150

def to_hash
  { weeks: weeks, days: days, hours: hours, minutes: minutes, seconds: seconds }
end

#to_hoursObject



110
# File 'lib/philiprehberger/duration.rb', line 110

def to_hours = @total_seconds / 3600.0

#to_humanObject



115
# File 'lib/philiprehberger/duration.rb', line 115

def to_human = Formatter.to_human(@total_seconds)

#to_iObject



113
# File 'lib/philiprehberger/duration.rb', line 113

def to_i = @total_seconds.to_i

#to_iso8601Object



116
# File 'lib/philiprehberger/duration.rb', line 116

def to_iso8601 = Formatter.to_iso8601(@total_seconds)

#to_minutesObject



109
# File 'lib/philiprehberger/duration.rb', line 109

def to_minutes = @total_seconds / 60.0

#to_sObject



191
# File 'lib/philiprehberger/duration.rb', line 191

def to_s = to_human

#to_secondsObject



108
# File 'lib/philiprehberger/duration.rb', line 108

def to_seconds = @total_seconds

#to_weeksObject



112
# File 'lib/philiprehberger/duration.rb', line 112

def to_weeks = @total_seconds / 604_800.0

#weeksInteger

Extracted week component

Returns:

  • (Integer)


120
121
122
# File 'lib/philiprehberger/duration.rb', line 120

def weeks
  @total_seconds.to_i / 604_800
end

#zero?Boolean

Whether this duration is zero seconds

Returns:

  • (Boolean)


83
84
85
# File 'lib/philiprehberger/duration.rb', line 83

def zero?
  @total_seconds.zero?
end