On Oct 19, 7:14 am, Ruby Quiz <ja... / grayproductions.net> wrote: > The three rules of Ruby Quiz: > > 1. Please do not post any solutions or spoiler discussion for this quiz until > 48 hours have passed from the time on this message. > > 2. Support Ruby Quiz by submitting ideas as often as you can: > > http://www.rubyquiz.com/ > > 3. Enjoy! > > Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone > on Ruby Talk follow the discussion. Please reply to the original quiz message, > if you can. > > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= > > by Brian Candler > > Write a Ruby class which can tell you whether the current time (or any given > time) is within a particular "time window". Time windows are defined by strings > in the following format: > > # 0700-0900 # every day between these times > # Sat Sun # all day Sat and Sun, no other times > # Sat Sun 0700-0900 # 0700-0900 on Sat and Sun only > # Mon-Fri 0700-0900 # 0700-0900 on Monday to Friday only > # Mon-Fri 0700-0900; Sat Sun # ditto plus all day Sat and Sun > # Fri-Mon 0700-0900 # 0700-0900 on Fri Sat Sun Mon > # Sat 0700-0800; Sun 0800-0900 # 0700-0800 on Sat, plus 0800-0900 on Sun > > Time ranges should exclude the upper bound, i.e. 0700-0900 is 07:00:00 to > 08:59:59. An empty time window means "all times everyday". Here are some test > cases to make it clearer: > > class TestTimeWindow < Test::Unit::TestCase > def test_window_1 > w = TimeWindow.new("Sat-Sun; Mon Wed 0700-0900; Thu 0700-0900 1000-1200") > > assert ! w.include?(Time.mktime(2007,9,25,8,0,0)) # Tue > assert w.include?(Time.mktime(2007,9,26,8,0,0)) # Wed > assert ! w.include?(Time.mktime(2007,9,26,11,0,0)) > assert ! w.include?(Time.mktime(2007,9,27,6,59,59)) # Thu > assert w.include?(Time.mktime(2007,9,27,7,0,0)) > assert w.include?(Time.mktime(2007,9,27,8,59,59)) > assert ! w.include?(Time.mktime(2007,9,27,9,0,0)) > assert w.include?(Time.mktime(2007,9,27,11,0,0)) > assert w.include?(Time.mktime(2007,9,29,11,0,0)) # Sat > assert w.include?(Time.mktime(2007,9,29,0,0,0)) > assert w.include?(Time.mktime(2007,9,29,23,59,59)) > end > > def test_window_2 > w = TimeWindow.new("Fri-Mon") > assert ! w.include?(Time.mktime(2007,9,27)) # Thu > assert w.include?(Time.mktime(2007,9,28)) > assert w.include?(Time.mktime(2007,9,29)) > assert w.include?(Time.mktime(2007,9,30)) > assert w.include?(Time.mktime(2007,10,1)) > assert ! w.include?(Time.mktime(2007,10,2)) # Tue > end > > def test_window_nil > w = RDS::TimeWindow.new("") > assert w.include?(Time.mktime(2007,9,25,1,2,3)) # all times > end > end Like Gordon, I used Runt a bit for my solution. Unlike Gordon, I didn't use Runt *directly*. I remembered seeing it some time ago and used what I could recall of the general ideas of implementation to roll my own (probably not as well as Runt itself). And I believe the naming of "Unbound Time" comes from Martin Fowler. require 'date' class TimeWindow attr_reader :intervals def initialize(string) @intervals = [] parse(string) end def include?(time) intervals.any? { |int| int.include?(time) } end private attr_writer :intervals def parse(string) parts = string.split(';') parts = [''] if parts.empty? @intervals = parts.collect { |str| TimeInterval.new(str) } end end class TimeInterval DAYS = %w(Sun Mon Tue Wed Thu Fri Sat) UnboundTime = Struct.new(:hour, :minute) do include Comparable def <=>(time) raise TypeError, "I need a real Time object for comparison" unless time.is_a?(Time) comp_date = Date.new(time.year, time.month, time.mday) comp_date += 1 if hour == 24 Time.mktime(comp_date.year, comp_date.month, comp_date.day, hour % 24, minute, 0) <=> time end end UnboundTimeRange = Struct.new(:start, :end) attr_reader :days, :times def initialize(string) @days = [] @times = [] parse(string) end def include?(time) day_ok?(time) and time_ok?(time) end private attr_writer :days, :times def parse(string) unless string.empty? string.strip.split(' ').each do |segment| if md = segment.match(/^(\d{4})-(\d{4})$/) self.times += [ UnboundTimeRange.new(UnboundTime.new(*md[1].unpack('A2A2').collect { |elem| elem.to_i }), UnboundTime.new(*md[2].unpack('A2A2').collect { |elem| elem.to_i })) ] elsif md = segment.match(/^(\w+)(-(\w+))?$/) if md[2] start_day = DAYS.index(md[1]) end_day = DAYS.index(md[3]) if start_day <= end_day self.days += (start_day .. end_day).to_a else self.days += (start_day .. DAYS.length).to_a + (0 .. end_day).to_a end else self.days += [DAYS.index(md[1])] end else raise ArgumentError, "Segment #{segment} of time window incomprehensible" end end end self.days = 0..DAYS.length if days.empty? self.times = [ UnboundTimeRange.new(UnboundTime.new(0, 0), UnboundTime.new(24, 0)) ] if times.empty? end def day_ok?(time) days.any? { |d| d == time.wday } end def time_ok?(time) times.any? { |t| t.start <= time and t.end > time } end end All tests pass, which at the moment is good enough for me. -- -yossef