class Hour

Attributes

h[R]
m[R]
s[R]

Public Class Methods

from(minutes: 0, seconds: 0) click to toggle source

Build an hour instance from either *minutes* or seconds. Unlike `.new`, either of these values can be over 60.

Hour.from(minutes: 85)  # => Hour.new(h: 1, m: 25)
Hour.from(seconds: 120) # => Hour.new(m: 2)
# File lib/hour.rb, line 93
def self.from(minutes: 0, seconds: 0)
  if minutes != 0 && seconds != 0
    raise ArgumentError.new("Use either minutes OR seconds, not both.")
  end

  if minutes > 0 || (minutes == 0 && seconds == 0)
    self.new(h: minutes / 60, m: minutes % 60)
  else
    self.from(minutes: seconds / 60) + self.new(s: seconds % 60)
  end
end
from_time(time, s: true) click to toggle source

TODO: document and write tests.

# File lib/hour.rb, line 64
def self.from_time(time, s: true)
  self.new(time.hour, time.min, s ? time.sec : false)
end
new(*args) click to toggle source

Build an hour instance from h, m and s. Raises an argument error if m or s is a value over 60.

For instantiating this class from a minutes or seconds value over 60, use `.from`.

# File lib/hour.rb, line 111
def initialize(*args)
  if args.length == 1 && args.first.is_a?(Hash)
    initialize_from_keyword_args(**args.first)
  else
    # Pad with 0s.
    args = args + Array.new(3 - args.length, 0)
    @h, @m, @s = args
  end

  if @m > 60
    raise ArgumentError.new("Minutes must be a number between 0 and 60.")
  end

  if @s.respond_to?(:round) && @s > 60
    raise ArgumentError.new("Seconds must be a number between 0 and 60.")
  end
end
now(**opts) click to toggle source

TODO: Test me and document me.

# File lib/hour.rb, line 59
def self.now(**opts)
  self.from_time(Time.now, **opts)
end
parse(serialised_hour, s: true) click to toggle source

Build an hour instance from an hour string.

Hour.parse("1:00:00")
Hour.parse("1:00", "%h:%m?") # Will work with "1:00" or just "1".

TODO: Implement me, test me and document me.

# File lib/hour.rb, line 74
def self.parse(serialised_hour, s: true)
  args = serialised_hour.split(':').map(&:to_i)

  if args.length == 3 && s
    self.new(*args)
  elsif args.length == 2 && !s
    self.new(*(args << false))
  elsif ((0..2).include?(args.length) && s) || ((0..1).include?(args.length) && !s)
    raise ArgumentError, "Too few segments (#{args.inspect})."
  elsif ((4..Float::INFINITY).include?(args.length) && s) || ((3..Float::INFINITY).include?(args.length) && !s)
    raise ArgumentError, "Too many segments (#{args.inspect})."
  end
end

Public Instance Methods

*(integer) click to toggle source
# File lib/hour.rb, line 166
def *(integer)
  raise ArgumentError, "must be an integer" unless integer.integer?
  self.class.from(seconds: (@h * integer * 3600) + (@m * integer * 60) + (@s * integer))
end
+(other) click to toggle source

Returns a new Hour instance returning the total time of the two hour instances.

Hour.new(m: 25, s: 10) + Hour.new(h: 1) # => Hour.new(1, 25, 10)
# File lib/hour.rb, line 132
def +(other)
  hours = @h + other.h + (@m + other.m + ((@s + other.s) / 60)) / 60
  minutes = (@m + other.m + ((@s + other.s) / 60)) % 60

  if @s && other.s
    seconds = (@s + other.s) % 60
  elsif (!@s) && (!other.s)
    seconds = false
  else
    raise "TODO: how to resolve this?"
  end

  self.class.new(hours, minutes, seconds)
end
-(other) click to toggle source
# File lib/hour.rb, line 147
def -(other)
  if other.to_decimal > self.to_decimal
    raise ArgumentError, "Negative hours not supported"
  end

  hours = @h - other.h - (@m - other.m - ((@s - other.s) / 60)) / 60
  minutes = (@m - other.m - ((@s - other.s) / 60)) % 60

  if @s && other.s
    seconds = (@s - other.s) % 60
  elsif (!@s) && (!other.s)
    seconds = false
  else
    raise "TODO: how to resolve this?"
  end

  self.class.new(hours, minutes, seconds)
end
hours() click to toggle source

Returns a decorator providing convenience methods for working with hours.

Hour.new(1, 25).hours.round # => 1
Hour.new(1, 45).hours.round # => 2
# File lib/hour.rb, line 175
def hours
  HourUnit.new(self)
end
inspect(format = nil)
Alias for: to_s
minutes() click to toggle source

Returns a decorator providing convenience methods for working with minutes.

Hour.new(1, 25, 52).minutes.value       # => 25
Hour.new(1, 25, 52).minutes.round       # => 26
Hour.new(1, 25, 52).minutes.total       # => 85
Hour.new(1, 25, 52).minutes.round_total # => 86
# File lib/hour.rb, line 185
def minutes
  MinuteUnit.new(self)
end
seconds() click to toggle source

Returns a decorator providing convenience methods for working with seconds.

Hour.new(m: 1, s: 10).seconds.value # => 10
Hour.new(1, 45, 10  ).seconds.total # => (1 * 60 * 60) + (45 * 60) + 10
# File lib/hour.rb, line 193
def seconds
  SecondUnit.new(self) if @s
end
to_decimal() click to toggle source

Provisional.

# File lib/hour.rb, line 210
def to_decimal
  decimal = (@m / 60.0) + (@s / 3600.0)
  "#{@h}.#{decimal}"
  @h + decimal
end
to_s(format = nil) click to toggle source

Returns string representation of the hour instance.

Hour.new(m: 1, s: 10 ).to_s # => "1:10"
Hour.new(1, 45, false).to_s # => "1:45"

TODO: Allow formatting string (to format hours to 2 digits etc).

# File lib/hour.rb, line 203
def to_s(format = nil)
  [(@h unless @h.zero?), format('%02d', @m), (format('%02d', @s) if @s)].compact.join(':')
end
Also aliased as: inspect
to_time(today = Time.now) click to toggle source
# File lib/hour.rb, line 216
def to_time(today = Time.now)
  Time.new(today.year, today.month, today.day, self.hours, self.minutes_over_the_hour)
end

Private Instance Methods

initialize_from_keyword_args(h: 0, m: 0, s: 0) click to toggle source
# File lib/hour.rb, line 233
def initialize_from_keyword_args(h: 0, m: 0, s: 0)
  @h, @m, @s = h, m, s
end