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