Class: Tins::Duration

Inherits:
Object show all
Includes:
Comparable
Defined in:
lib/tins/duration.rb

Overview

A class to represent durations with support for formatting and parsing time intervals.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(seconds) ⇒ Duration

Initializes a new Duration object with the specified number of seconds.

represent

Parameters:

  • seconds (Integer, Float)

    the total number of seconds to



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/tins/duration.rb', line 78

def initialize(seconds)
  @negative         = seconds < 0
  seconds           = seconds.abs
  @original_seconds = seconds
  @days, @hours, @minutes, @seconds, @fractional_seconds =
    [ 86_400, 3600, 60, 1, 0 ].inject([ [], seconds ]) {|(r, s), d|
      if d > 0
        dd, rest = s.divmod(d)
        r << dd
        [ r, rest ]
      else
        r << s
      end
    }
end

Class Method Details

.parse(string, template: '%S%d+%h:%m:%s.%f') ⇒ Integer, Float

Returns the number of seconds represented by the given duration string according to the provided template format.

The parser supports the following directives in templates:

  • ‘%S` - Sign indicator (optional negative sign)

  • ‘%d` - Days component (integer)

  • ‘%h` - Hours component (integer)

  • ‘%m` - Minutes component (integer)

  • ‘%s` - Seconds component (integer)

  • ‘%f` - Fractional seconds component (decimal)

  • ‘%%` - Literal percent character

The parser is greedy and consumes as much of the input string as possible for each directive. If a directive expects a specific format but doesn’t find it, an ArgumentError is raised.

Examples:

Basic parsing

Tins::Duration.parse('6+05:04:03', template: '%S%d+%h:%m:%s') # => 536643
Tins::Duration.parse('6+05:04:03.21', template: '%S%d+%h:%m:%s.%f') # => 536643.21

Parsing negative durations

Tins::Duration.parse('-6+05:04:03', template: '%S%d+%h:%m:%s') # => -536643

Custom template parsing

Tins::Duration.parse('05:04:03.21', template: '%h:%m:%s.%f') # => 18243.21

Parameters:

  • string (String)

    The duration string to parse.

  • template (String) (defaults to: '%S%d+%h:%m:%s.%f')

    for the duration format, see #format. Default: ‘%S%d+%h:%m:%s.%f’

Returns:

  • (Integer, Float)

    The number of (fractional) seconds of the duration.

Raises:

  • (ArgumentError)

    If the string doesn’t match the expected template format



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/tins/duration.rb', line 43

def self.parse(string, template: '%S%d+%h:%m:%s.%f')
  s, t  = string.to_s.dup, template.dup
  d, sd = 0, 1
  loop do
    t.sub!(/\A(%[Sdhmsf%]|.)/) { |directive|
      case directive
      when '%S' then s.sub!(/\A-?/)   { sd *= -1 if _1 == ?-; '' }
      when '%d' then s.sub!(/\A\d+/)  { d += 86_400 * _1.to_i; '' }
      when '%h' then s.sub!(/\A\d+/)  { d += 3_600 * _1.to_i; '' }
      when '%m' then s.sub!(/\A\d+/)  { d += 60 * _1.to_i; '' }
      when '%s' then s.sub!(/\A\d+/)  { d += _1.to_i; '' }
      when '%f' then s.sub!(/\A\d+/)  { d += Float(?. + _1); '' }
      when '%%' then
        if s[0] == ?%
          s[0] = ''
        else
          raise "expected %s, got #{s.inspect}"
        end
      else
        if directive == s[0]
          s[0] = ''
        else
          raise ArgumentError, "expected #{t.inspect}, got #{s.inspect}"
        end
      end
      ''
    } or break
  end
  sd * d
end

Instance Method Details

#<=>(other) ⇒ Integer

The <=> method compares this object with another object after converting both to floats.

equal, 1 if this object is greater than other

Parameters:

  • other (Object)

    the object to compare with

Returns:

  • (Integer)

    -1 if this object is less than other, 0 if they are



108
109
110
# File 'lib/tins/duration.rb', line 108

def <=>(other)
  to_f <=> other.to_f
end

#days?TrueClass, FalseClass

Returns true if the duration includes days, false otherwise.

otherwise

Returns:

  • (TrueClass, FalseClass)

    true if the duration has any days, false



124
125
126
# File 'lib/tins/duration.rb', line 124

def days?
  @days > 0
end

#format(template = '%S%d+%h:%m:%s.%f', precision: nil) ⇒ String

Formats the duration according to the given template and precision.

The template string supports the following directives:

  • ‘%S` - Sign indicator (negative sign if duration is negative)

  • ‘%d` - Days component

  • ‘%h` - Hours component (zero-padded to 2 digits)

  • ‘%m` - Minutes component (zero-padded to 2 digits)

  • ‘%s` - Seconds component (zero-padded to 2 digits)

  • ‘%f` - Fractional seconds component (without the leading decimal point)

  • ‘%D` - Smart format (automatically includes days, fractional seconds, and sign)

  • ‘%%` - Literal percent character

When using ‘%f`, the fractional part will be formatted according to the precision parameter.

Examples:

Basic formatting

duration = Tins::Duration.new(93784.123)
duration.format('%d+%h:%m:%s.%f') # => "1+02:03:04.123000"

Smart format

duration.format('%D') # => "1+02:03:04.123"

Custom precision

duration.format('%s.%f', precision: 2) # => "04.12"

Parameters:

  • template (String) (defaults to: '%S%d+%h:%m:%s.%f')

    the format template to use for formatting Default: ‘%S%d+%h:%m:%s.%f’

  • precision (Integer) (defaults to: nil)

    the precision to use for fractional seconds When nil, uses default formatting with 6 decimal places

Returns:

  • (String)

    the formatted duration string



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

def format(template = '%S%d+%h:%m:%s.%f', precision: nil)
  result = template.gsub(/%[DdhmSs%]/) { |directive|
    case directive
    when '%S' then ?- if negative?
    when '%d' then @days
    when '%h' then '%02u' % @hours
    when '%m' then '%02u' % @minutes
    when '%s' then '%02u' % @seconds
    when '%D' then format_smart
    when '%%' then '%'
    end
  }
  if result.include?('%f')
    if precision
      fractional_seconds = "%.#{precision}f" % @fractional_seconds
    else
      fractional_seconds = '%f' % @fractional_seconds
    end
    result.gsub!('%f', fractional_seconds[2..-1])
  end
  result
end

#format_smartString (private)

The format_smart method provides intelligent formatting based on the duration’s components. It automatically determines which components are present and formats them accordingly, making it ideal for human-readable output where you want to avoid showing zero values.

The smart format follows these rules:

  • If days are present, includes the day component (e.g., “1+02:03:04”) -

If fractional seconds are present, includes them with 3 decimal places (e.g., “.123”)

  • If the duration is negative, includes the sign prefix

This method is used internally by #to_s and can also be called directly for smart formatting using the %D directive.

inclusion

Returns:

  • (String)

    a formatted duration string using intelligent component



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/tins/duration.rb', line 238

def format_smart
  template  = '%h:%m:%s'
  precision = nil
  if days?
    template.prepend '%d+'
  end
  if fractional_seconds?
    template << '.%f'
    precision = 3
  end
  template.prepend '%S'
  format template, precision: precision
end

#fractional_seconds?TrueClass, FalseClass

Returns true if the duration includes fractional seconds.

false otherwise

Returns:

  • (TrueClass, FalseClass)

    true if fractional seconds are present,



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

def fractional_seconds?
  @fractional_seconds > 0
end

#hours?TrueClass, FalseClass

Returns true if the duration has any hours component

otherwise

Returns:

  • (TrueClass, FalseClass)

    true if hours are present, false



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

def hours?
  @hours > 0
end

#minutes?TrueClass, FalseClass

Returns true if the duration has minutes, false otherwise.

Returns:

  • (TrueClass, FalseClass)

    true if minutes are greater than 0, false otherwise



139
140
141
# File 'lib/tins/duration.rb', line 139

def minutes?
  @minutes > 0
end

#negative?TrueClass, FalseClass

Returns true if the duration is negative.

negative time interval, false otherwise

Returns:

  • (TrueClass, FalseClass)

    true if the duration represents a



116
117
118
# File 'lib/tins/duration.rb', line 116

def negative?
  @negative
end

#seconds?TrueClass, FalseClass

Returns true if the duration has a positive seconds component.

false otherwise

Returns:

  • (TrueClass, FalseClass)

    true if seconds are greater than zero,



147
148
149
# File 'lib/tins/duration.rb', line 147

def seconds?
  @seconds > 0
end

#to_fFloat

Converts the original seconds value to a floating-point number.

Returns:

  • (Float)

    the original seconds value as a floating-point number



97
98
99
# File 'lib/tins/duration.rb', line 97

def to_f
  @original_seconds.to_f
end

#to_sString

Returns the formatted duration string.

Returns:

  • (String)

    the formatted duration string



216
217
218
# File 'lib/tins/duration.rb', line 216

def to_s
  format_smart
end