Here is my solution (I extracted the Numeric extensions into a separate
core_ext.rb along with an extension to Time):
The key thing in the conflict resolution was partitioning the weekly
from the specific candidate programs and picking the most recent
addition.
Cheers.
# BEGIN -- program_manager.rb --
require 'core_ext'
require 'program'
class ProgramManager
def initialize
@programs = []
end
# Query to determine if we should be recording at any particular
# moment. It can be assumed that the VCR will query the program
# manager at most twice per minute, and with always increasing
minutes.
# New programs may be added between two calls to #record?.
#
# This method must return either a +nil+, indicating to stop
recording,
# or don't start, or an +Integer+, which is the channel number we
should
# be recording.
def record?(time)
candidates = @programs.select { |p| p.on?(time) }
weekly, specific = candidates.partition { |p| p.weekly? }
return specific.last.channel unless specific.empty?
return weekly.last.channel unless weekly.empty?
nil
end
# Adds a new Program to the list of programs to record.
def add(program)
@programs << Program.new(program)
end
end
# END -- program_manager.rb --
# BEGIN -- program.rb --
require 'core_ext'
class Program
WEEKDAYS = { 'sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3,
'thu' => 4, 'fri' => 5, 'sat' => 6 }
attr_reader :start, :end, :channel, :days
def initialize(program)
@start = program[:start]
@end = program[:end]
@channel = program[:channel]
@days = program[:days]
raise "Missing start or end" if @start.nil? || @end.nil?
raise "Wrong start or end types" unless (@start.is_a?(Time) &&
@end.is_a?(Time)) ||
(@start.is_a?(Integer) &&
@end.is_a?(Integer))
raise "Invalid program" if weekly? && (@start.is_a?(Time) ||
@end.is_a?(Time))
raise "End must come after Start" if !weekly? && @start > @end
raise "Missing channel" if !@channel.is_a?(Integer)
raise "Invalid weekday" if @days.is_a?(Array) && @days.any? { |day|
WEEKDAYS[day] == nil }
end
def weekly?
!@days.nil?
end
def on?(time)
if weekly? #weekly program
for day in @days
if WEEKDAYS[day] == time.wday
return @channel if time.secs >= @start && time.secs <= @end
end
end
else #specific time
return @channel if time >= @start && time <= @end
end
nil
end
end
# END -- program.rb --
# BEGIN -- core_ext.rb --
class Numeric
# Number of seconds since midnight
def hours
(self * 60).minutes
end
def minutes
self * 60
end
def seconds
self
end
alias_method :hour, :hours
alias_method :minute, :minutes
alias_method :second, :seconds
end
class Time
def secs
self.hour * 3600 + self.min * 60 + self.sec
end
end
# END -- core_ext.rb --