Re: Time Window (#144)



On Oct 19, 7:14 am, Ruby Quiz <ja...@xxxxxxxxxxxxxxxxxxx> 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


.



Relevant Pages

  • Re: [QUIZ] Time Window (#144)
    ... Please do not post any solutions or spoiler discussion for this quiz ... Support Ruby Quiz by submitting ideas as often as you can: ... def test_window_1 ... assert w.include?), ...
    (comp.lang.ruby)
  • Re: testing a singleton method....
    ... Learn Ruby on Rails! ... def test_singleton_method ... assert string1.respond_to? ...
    (comp.lang.ruby)
  • Re: [QUIZ] Time Window (#144)
    ... Please do not post any solutions or spoiler discussion for this quiz ... Support Ruby Quiz by submitting ideas as often as you can: ... def test_window_1 ... w.include?) assert! ...
    (comp.lang.ruby)
  • Re: Why are there no ordered dictionaries?
    ... >> Do you really mean just re-ordering the keys without a corresponding reording of values?? ... style accesses, with assign, eval, and del available for the latter two. ... Some def test_usecase_xx: ... ...
    (comp.lang.python)
  • Re: cleaner way to write this try/except statement?
    ... def Validate(self, parent): ... wx.MessageBox('Enter a valid time.', 'Invalid time entered', ... well, assuming you are unsatisfied with the above, you could try to assert the ...
    (comp.lang.python)