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

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)

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