Class: Hour

Inherits:
Object
  • Object
show all
Defined in:
lib/hour.rb

Defined Under Namespace

Classes: HourUnit, MinuteUnit, SecondUnit, Unit

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Hour

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



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# 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

Instance Attribute Details

#hObject (readonly)

Returns the value of attribute h.



105
106
107
# File 'lib/hour.rb', line 105

def h
  @h
end

#mObject (readonly)

Returns the value of attribute m.



105
106
107
# File 'lib/hour.rb', line 105

def m
  @m
end

#sObject (readonly)

Returns the value of attribute s.



105
106
107
# File 'lib/hour.rb', line 105

def s
  @s
end

Class Method Details

.from(minutes: 0, seconds: 0) ⇒ Object

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)


93
94
95
96
97
98
99
100
101
102
103
# 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) ⇒ Object

TODO: document and write tests.



64
65
66
# File 'lib/hour.rb', line 64

def self.from_time(time, s: true)
  self.new(time.hour, time.min, s ? time.sec : false)
end

.now(**opts) ⇒ Object

TODO: Test me and document me.



59
60
61
# File 'lib/hour.rb', line 59

def self.now(**opts)
  self.from_time(Time.now, **opts)
end

.parse(serialised_hour, s: true) ⇒ Object

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.



74
75
76
77
78
79
80
81
82
83
84
85
86
# 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

Instance Method Details

#*(integer) ⇒ Object

Raises:

  • (ArgumentError)


166
167
168
169
# 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) ⇒ Object

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)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
# 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) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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

#hoursObject

Returns a decorator providing convenience methods for working with hours.

Hour.new(1, 25).hours.round # => 1
Hour.new(1, 45).hours.round # => 2


175
176
177
# File 'lib/hour.rb', line 175

def hours
  HourUnit.new(self)
end

#minutesObject

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


185
186
187
# File 'lib/hour.rb', line 185

def minutes
  MinuteUnit.new(self)
end

#secondsObject

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


193
194
195
# File 'lib/hour.rb', line 193

def seconds
  SecondUnit.new(self) if @s
end

#to_decimalObject

Provisional.



210
211
212
213
214
# 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) ⇒ Object Also known as: inspect

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



203
204
205
# 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

#to_time(today = Time.now) ⇒ Object



216
217
218
# 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