[QUIZ] Time Window (#144)

Discussion in 'Ruby' started by Ruby Quiz, Oct 19, 2007.

  1. Ruby Quiz

    Ruby Quiz Guest

    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
    Ruby Quiz, Oct 19, 2007
    #1
    1. Advertising

  2. Ruby Quiz

    Ken Bloom Guest

    On Fri, 19 Oct 2007 21:14:00 +0900, Ruby Quiz 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:
    >



    I have rewritten the test cases to give more informative messages:

    class TestTimeWindow < Test::Unit::TestCase
    def test_window_1
    s = "Sat-Sun; Mon Wed 0700-0900; Thu 0700-0900 1000-1200"
    w = TimeWindow.new(s)

    assert ! w.include?(Time.mktime(2007,9,25,8,0,0)), "#{s.inspect} should not include Tue 8am"
    assert w.include?(Time.mktime(2007,9,26,8,0,0)), "#{s.inspect} should include Wed 8am"
    assert ! w.include?(Time.mktime(2007,9,26,11,0,0)), "#{s.inspect} should not include Wed 11am"
    assert ! w.include?(Time.mktime(2007,9,27,6,59,59)), "#{s.inspect} should not include Thurs 6:59am"
    assert w.include?(Time.mktime(2007,9,27,7,0,0)), "#{s.inspect} should include Thurs 7am"
    assert w.include?(Time.mktime(2007,9,27,8,59,59)), "#{s.inspect} should include Thurs 8:59am"
    assert ! w.include?(Time.mktime(2007,9,27,9,0,0)), "#{s.inspect} should not include Thurs 9am"
    assert w.include?(Time.mktime(2007,9,27,11,0,0)), "#{s.inspect} should include Thurs 11am"
    assert w.include?(Time.mktime(2007,9,29,11,0,0)), "#{s.inspect} should include Sat 11am"
    assert w.include?(Time.mktime(2007,9,29,0,0,0)), "#{s.inspect} should include Sat midnight"
    assert w.include?(Time.mktime(2007,9,29,23,59,59)),
    "#{s.inspect} should include Saturday one minute before midnight"
    end

    def test_window_2
    s = "Fri-Mon"
    w = TimeWindow.new(s)
    assert ! w.include?(Time.mktime(2007,9,27)), "#{s.inspect} should not include Thurs"
    assert w.include?(Time.mktime(2007,9,28)), "#{s.inspect} should include Fri"
    assert w.include?(Time.mktime(2007,9,29)), "#{s.inspect} should include Sat"
    assert w.include?(Time.mktime(2007,9,30)), "#{s.inspect} should include Sun"
    assert w.include?(Time.mktime(2007,10,1)), "#{s.inspect} should include Mon"
    assert ! w.include?(Time.mktime(2007,10,2)), "#{s.inspect} should not include Tues"
    end

    def test_window_nil
    w = TimeWindow.new("")
    assert w.include?(Time.mktime(2007,9,25,1,2,3)),"Empty string should include all times"
    end
    end




    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
    Ken Bloom, Oct 19, 2007
    #2
    1. Advertising

  3. Ruby Quiz

    Eric I. Guest

    Solution to Time Window (#144)

    Below you'll find my solution to the quiz. I approached it a
    relatively standard object-oriented fashion. For example,
    TimeSpecifier acts as an abstract base class for Day and HourMinute to
    bring together their commonalities. And I did use some of that good
    ol' Ruby duck typing so that a TimeRange can be used as a
    TimeSpecifier.

    Eric

    ----

    Are you interested in on-site Ruby training that uses well-designed,
    real-world, hands-on exercises? http://LearnRuby.com

    ====

    # This is a solution to Ruby Quiz #144 (see http://www.rubyquiz.com/)
    # by LearnRuby.com and released under the Creative Commons
    # Attribution-Share Alike 3.0 United States License. This source code
    can
    # also be found at:
    # http://learnruby.com/examples/ruby-quiz-144.shtml


    # A TimeWindow is a specification for a time window. It is specified
    # by a string, and an instance of Time can be checked to see if it's
    # included in the window. The specification string is is best
    # documented by quoting the Ruby Quiz #144 description:
    #
    # 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
    class TimeWindow


    # Represents a time range defined by a start and end TimeSpecifier.
    class TimeRange
    def initialize(start_t, end_t,
    include_end, allow_reverse_range = false)
    raise "mismatched time specifiers in range (%s and %s)" %
    [start_t, end_t] unless
    start_t.class == end_t.class
    raise "reverse range not allowed \"%s-%s\"" % [start_t, end_t]
    if
    start_t >= end_t && !allow_reverse_range
    @start_t, @end_t, @include_end = start_t, end_t, include_end
    end

    # Equality is defined as a TimeSpecifier on the RHS being in the
    # this range.
    def ==(time_spec)
    # do either a < or a <= when comparing the end of the range
    # depending on value of @include_end
    end_comparison = @include_end ? :<= : :<

    # NOTE: the call to the send method below is used to call the
    # method in end_comparison
    if @start_t < @end_t
    time_spec >= @start_t && time_spec.send(end_comparison,
    @end_t)
    else # a reverse range, such as "Fri-Mon", needs an ||
    time_spec >= @start_t || time_spec.send(end_comparison,
    @end_t)
    end
    end

    def to_s
    "%s-%s" % [@start_t, @end_t]
    end
    end


    # This is an abstract base class for time specifiers, such as a day
    # of the week or a time of day.
    class TimeSpecifier
    include Comparable

    def <=>(other)
    raise "incompatible comparison (%s and %s)" % [self, other]
    unless
    self.class == other.class
    @specifier <=> other.specifier
    end

    protected

    attr_reader :specifier

    # Given an "item" regular expression returns a hash of two regular
    # expressions. One matches an individual item and the other a
    # range of items. Both returned regular expressions use parens,
    # so the individual items can be extraced from a match.
    def self.build_regexps(regexp)
    individual_re = Regexp.new "^(%s)" % regexp
    range_re = Regexp.new "^(%s)\-(%s)" % [regexp, regexp]
    { :individual => individual_re, :range => range_re }
    end

    # Attempts to match str with the two regexps passed in. regexps
    # is a hash that contains two regular expressions, one that
    # matches a single TimeSpecifier and one that matches a range of
    # TimeSpecifiers. If there's a match then it returns either an
    # instance of klass or an instance of a TimeRange of klass (and
    # str is destructively modified to remove the matched text from
    # its beginning). If there isn't a match, then nil is returned.
    # include_end determines whether the end specification of the
    # range is included in the range (e.g., if the specifier is
    # "Mon-Fri" whether or not Fri is included). allow_reverse_range
    # determines whether a range in which the start is after the end
    # is allowed, as in "Fri-Mon"; this might be alright for days of
    # the week but not for times.
    def self.super_parse(str, klass, regexps,
    include_end, allow_reverse_range)
    # first try a range match
    if match_data = regexps[:range].match(str)
    consume_front(str, match_data[0].size)
    TimeRange.new(klass.new_from_str(match_data[1]),
    klass.new_from_str(match_data[2]),
    include_end,
    allow_reverse_range)
    # second try individual match
    elsif match_data = regexps[:individual].match(str)
    consume_front(str, match_data[0].size)
    klass.new_from_str(match_data[1])
    else
    nil
    end
    end

    # Consumes size characters from the front of str along with any
    # remaining whitespace at the front. This modifies the actual
    # string.
    def self.consume_front(str, size)
    str[0..size] = ''
    str.lstrip!
    end
    end


    # Time specifier for a day of the week.
    class Day < TimeSpecifier
    Days = %w(Sun Mon Tue Wed Thu Fri Sat)
    @@regexps = TimeSpecifier.build_regexps(/[A-Za-z]{3}/)

    def initialize(day)
    raise "illegal day \"#{day}\"" unless (0...Days.size) === day
    @specifier = day
    end

    def to_s
    Days[@specifier]
    end

    def self.new_from_str(str)
    day = Days.index(str)
    raise "illegal day \"#{day_str}\"" if day.nil?
    new(day)
    end

    def self.parse(str)
    super_parse(str, Day, @@regexps, true, true)
    end
    end


    # Time specifier for a specific time of the day (i.e., hour and
    minute).
    class HourMinute < TimeSpecifier
    @@regexps = TimeSpecifier.build_regexps(/\d{4}/)

    def initialize(hour_minute)
    hour = hour_minute / 100
    minute = hour_minute % 100
    raise "illegal time \"#{hour_minute}\"" unless
    (0..23) === hour && (0..59) === minute
    @specifier = hour_minute
    end

    def to_s
    "%04d" % @specifier
    end

    def self.new_from_str(str)
    new str.to_i
    end

    def self.parse(str)
    super_parse(str, HourMinute, @@regexps, false, false)
    end
    end


    # Creates a TimeWindow by parsing a string specifying some
    combination
    # of day and hour-minutes, possibly in ranges.
    def initialize(str)
    # time_frame is a Day, HourMinute, or TimeRangeof either; it is
    # set here so when it's sent inside the block, it won't be scoped
    # to the block
    time_frame = nil

    @periods = []
    str.split(/ *; */).each do |period_str|
    # frame set is a hash where the keys are either the class Day or
    # HourMinute and the associated values are all time specifiers
    # for that class. The default value is the empty array.
    period = Hash.new { |h, k| h[k] = [] }

    # process each time specifier in period_str by sequentially
    # processing andconsuming the beginning of the string
    until period_str.empty?
    # set frame_type and time_frame based on the first matching
    # parse
    frame_type = [Day, HourMinute].find { |specifier|
    time_frame = specifier.parse(period_str)
    }
    raise "illegal window specifier \"#{period_str}\"." if
    time_frame.nil?

    period[frame_type] << time_frame
    end

    @periods << period
    end
    end

    # Returns true if the TimeWindow includes the passed in time, false
    # otherwise.
    def include?(time)
    d = Day.new(time.wday)
    hm = HourMinute.new(time.hour * 100 + time.min)

    # see if any one period matches the time or if there are no
    periods
    @periods.empty? || @periods.any? { |period|
    # a period matches if either there is no day specification or
    # one day specification matches, and if either there is no
    # hour-minute specification or one such specification matches
    (period[Day].empty? ||
    period[Day].any? { |day_period| day_period == d }) &&
    (period[HourMinute].empty? ||
    period[HourMinute].any? { |hm_period| hm_period == hm })
    }
    end

    def to_s
    @periods.map { |period|
    (period[Day] + period[HourMinute]).map { |time_spec|
    time_spec.to_s
    }.join(' ')
    }.join(' ; ')
    end
    end
    Eric I., Oct 21, 2007
    #3
  4. Here's my solutions. I used Runt for the heavy lifting. I just had
    to parse the string and create Runt temporal expressions.

    require 'runt'

    #adds ability to check Runt expressions against Time objects
    class Time
    include Runt::DPrecision

    attr_accessor :date_precision

    def date_precision
    return @date_precision unless @date_precision.nil?
    return Runt::DPrecision::DEFAULT
    end
    end

    module Runt

    #extends REWeek to allow for spanning across weeks
    class REWeek

    def initialize(start_day,end_day=6)
    @start_day = start_day
    @end_day = end_day
    end

    def include?(date)
    return true if @start_day==@end_day
    if @start_day < @end_day
    @start_day<=date.wday && @end_day>=date.wday
    else
    (@start_day<=date.wday && 6 >=date.wday) ||
    (0 <=date.wday && @end_day >=date.wday)
    end
    end

    end

    class StringParser < Runt::Intersect

    def initialize(string)
    super()
    add parsed(string)
    end

    #recursive method to parse input string
    def parse(token)
    case token
    when ""
    REWeek.new(0)
    when /^(.+);(.+)/ # split at semicolons
    parse($1) | parse($2)
    when /(\D+) (\d.+)/ # split days and times
    parse($1) & parse($2)
    when /(\D+) (\D+)/, /(\d+-\d+) (\d+-\d+)/ # split at spaces
    parse($1) | parse($2)
    when /([A-Z][a-z][a-z])-([A-Z][a-z][a-z])/ # create range of days
    REWeek.new(Runt.const_get($1), Runt.const_get($2))
    when /([A-Z][a-z][a-z])/ # create single day
    DIWeek.new(Runt.const_get($1))
    when /(\d\d)(\d\d)-(\d\d)(\d\d)/ #create time range
    start = Time.mktime(2000,1,1,$1.to_i,$2.to_i)
    # 0600-0900 should work like 0600-0859,
    stop = Time.mktime(2000,1,1,$3.to_i,$4.to_i) - 60
    REDay.new(start.hour, start.min, stop.hour, stop.min)
    end
    end
    alias :parsed :parse

    end

    end

    class TimeWindow < Runt::StringParser
    end
    Gordon Thiesfeld, Oct 21, 2007
    #4
  5. Ruby Quiz

    Juanger Guest

    Another solution for Time Window quiz:

    I considered only when input has day ranges in ascending order ("Mon
    Fri-Sun" or "Fri-Sun Mon", but not "Fri-Mon") and the first day of the
    week is Monday.


    class TimeWindow

    Days = { "Mon" => 0, "Tue" => 1, "Wed" => 2, "Thu" => 3, "Fri" => 4,
    "Sat" => 5, "Sun" => 6}

    def initialize (window)
    @window = window
    @ranges = []
    parse_window
    end

    def include? (time)
    hour = time.strftime("%H%M").to_i
    day = time.strftime("%w").to_i
    req = (day-1)*10000+hour
    puts "#{req}"
    result = false
    @ranges.each{ |range|
    if range[0] <= req && req < range[1]
    result = true
    end
    }
    result
    end

    private

    #Parse the input
    def parse_window
    regex = /((?:Mon[ -]?|Tue[ -]?|Wed[ -]?|Thu[ -]?|Fri[ -]?|Sat[
    -]?|Sun[ -]?)+)?((?:[012]\d[0-6]\d-[012]\d[0-6]\d[ ]?)+)?/
    @window.split(";").each { |window|
    window.strip!
    match = regex.match(window)

    # it has days
    if match[1]
    days = parse_days match[1]
    else
    days = [[0,6]] # everyday
    end

    # it has hours
    if match[2]
    time = parse_time match[2]
    else
    time = [[0,2400]] # all day
    end

    days.each {|dr|
    time.each {|tr|
    @ranges << [dr[0]*10000+tr[0], dr[1]*10000+tr[1]]
    }
    }
    }
    end

    def parse_days (days)
    result = []
    days.scan(/(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun)-(Mon|Tue|Wed|Thu|Fri|Sat|Sun)|(Mon|Tue|Wed|Thu|Fri|Sat|Sun))/)
    {
    if $3 # it's just one day
    result << [Days[$3],Days[$3]]
    else # it's a range
    result << [Days[$1],Days[$2]]
    end
    }
    result
    end

    def parse_time (time)
    result = []
    time.scan(/([012]\d[0-6]\d)-([012]\d[0-6]\d)/) {
    result << [$1.to_i, $2.to_i]
    }
    result
    end
    end
    Juanger, Oct 22, 2007
    #5
  6. Ruby Quiz

    Ken Bloom Guest

    On Fri, 19 Oct 2007 21:14:00 +0900, Ruby Quiz 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


    #!/usr/bin/env ruby

    class TimeWindow
    DAYNAMES=%w[Sun Mon Tue Wed Thu Fri Sat]
    DAYNAME=%r{Sun|Mon|Tue|Wed|Thu|Fri|Sat}
    TIME=%r{[0-9]+}

    def initialize string
    string = " " if string == "" #make an empty string match everythingworking around the way clauses are split
    #splitting an empty string gives an empty array (i.e. no clauses)
    #splitting a " " gives a single clause with no day names (so all are used) and no times (so all are used)
    @myarray=Array.new(7){[]}

    #different clauses are split by semicolons
    string.split(/\s*;\s*/).each do |clause|

    #find the days that this clause applies to
    curdays=[]
    clause.scan(/(#{DAYNAME})(?:(?=\s)|$)|(#{DAYNAME})-(#{DAYNAME})/) do |single,start,finish|
    single &&= DAYNAMES.index(single)
    start &&= DAYNAMES.index(start)
    finish &&= DAYNAMES.index(finish)
    curdays << single if single
    if start and finish
    (start..finish).each{|x| curdays << x} if start<finish
    (start..6).each{|x| curdays << x} if finish<start
    (0..finish).each{|x| curdays << x} if finish<start
    end
    end

    #all days if no day names were given
    curdays=(0..6).to_a if curdays==[]


    #find the times that this clause applies to
    found=false
    clause.scan(/(#{TIME})-(#{TIME})/) do |start,finish|
    found=true
    curdays.each do |day|
    @myarray[day] << [start,finish]
    end
    end

    #all times if none were given
    if not found
    curdays.each {|day| @myarray[day] << ["0000","2400"]}
    end
    end
    end

    def include? time
    matchday=time.wday
    matchtime="%02d%02d" % [time.hour,time.min]
    @myarray[matchday].any?{|start,finish| start<=matchtime && matchtime<finish}
    end

    alias_method :===, :include?

    end




    --
    Ken Bloom. PhD candidate. Linguistic Cognition Laboratory.
    Department of Computer Science. Illinois Institute of Technology.
    http://www.iit.edu/~kbloom1/
    Ken Bloom, Oct 22, 2007
    #6
  7. Re: Time Window (#144)

    On Oct 19, 7:14 am, Ruby Quiz <> 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
    Yossef Mendelssohn, Oct 22, 2007
    #7
  8. Ruby Quiz

    Philip Gatt Guest

    Here is my solution to the time window quiz. Range.create_from_string
    is the workhorse method and it would be nice if that was refactored
    into smaller pieces.

    class TimeWindow
    def initialize(definition_string)
    @ranges = []
    definition_string.split(/;/).each do |part|
    @ranges << Range.create_from_string(part.strip)
    end
    @ranges << Range.create_from_string('') if @ranges.empty?
    end

    def include?(time)
    @ranges.any? {|r| r.include?(time)}
    end

    class Range < Struct.new:)day_parts, :time_parts)
    DAYS = %w{Sun Mon Tue Wed Thu Fri Sat}

    def self.create_from_string(str)
    time_parts = []
    day_parts = []
    str.split(/ /).each do |token|
    token.strip!
    if DAYS.include?(token)
    day_parts << token
    elsif token =~ /^(\w{3})-(\w{3})$/
    start_day, end_day = $1, $2
    start_found = false
    (DAYS * 2).each do |d|
    start_found = true if d == start_day
    day_parts << d if start_found
    break if d == end_day && start_found
    end
    elsif token =~ /^(\d{4})-(\d{4})$/
    time_parts << (($1.to_i)..($2.to_i - 1))
    else
    raise "Unrecognized token: #{token}"
    end
    end
    time_parts << (0..2399) if time_parts.empty?
    day_parts = DAYS.clone if day_parts.empty?
    self.new(day_parts, time_parts)
    end

    def include?(time)
    matches_day?(time) && matches_minute?(time)
    end

    def matches_day?(time)
    day = time.strftime('%a')
    self.day_parts.include?(day)
    end

    def matches_minute?(time)
    minute = time.strftime('%H%M').to_i
    self.time_parts.any? {|tp| tp.include?(minute) }
    end
    end
    end
    Philip Gatt, Oct 22, 2007
    #8
  9. On 10/19/07, Ruby Quiz <> wrote:
    > 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:


    Hi,

    This is my solution: nothing spectacular or too clever. The idea was
    to convert every part of the window (everything between ";") into a
    class that knows how to parse the ranges. That class (TimeRange)
    converts the part into an array of day_of_week ranges and an array of
    hour ranges. To include a time, this window needs to match at least
    one day_of_week and at least one hour range. The time window, then,
    has an array of those TimeRange objects, and tries to find at least
    one that matches. One interesting thing is that I convert every time
    definition into a range, even the ones with just one element, so I can
    use Range#include? across all time ranges.

    require 'time'

    class TimeRange
    def initialize(s)
    @day_of_week = []
    @hour = []
    s.strip.split(" ").each do |range|
    if (match = range.match(/(\d{4})-(\d{4})/))
    @hour << (match[1].to_i...match[2].to_i)
    elsif (match = range.match(/([a-zA-Z]{3})-([a-zA-Z]{3})/))
    first = Time::RFC2822_DAY_NAME.index(match[1])
    second = Time::RFC2822_DAY_NAME.index(match[2])
    if (first < second)
    @day_of_week << (first..second)
    else
    @day_of_week << (first..(Time::RFC2822_DAY_NAME.size-1))
    @day_of_week << (0..second)
    end
    else
    @day_of_week <<
    (Time::RFC2822_DAY_NAME.index(range)..Time::RFC2822_DAY_NAME.index(range))
    end
    end
    end

    def include?(time)
    dow = time.wday
    hour = time.strftime("%H%M").to_i
    any?(@day_of_week, dow) and any?(@hour, hour)
    end

    def any?(enum, value)
    return true if enum.empty?
    enum.any?{|x| x.include?(value)}
    end
    end

    class TimeWindow
    def initialize(s)
    @ranges = []
    s.split(";").each do |part|
    @ranges << TimeRange.new(part)
    end
    end

    def include?(time)
    return true if @ranges.empty?
    @ranges.any? {|x| x.include?(time)}
    end
    end

    Kind regards,

    Jesus.
    Jesús Gabriel y Galán, Oct 22, 2007
    #9
  10. On 10/21/07, Gordon Thiesfeld <> wrote:
    > Here's my solutions. I used Runt for the heavy lifting. I just had
    > to parse the string and create Runt temporal expressions.


    There was a bug in my code. I shouldn't subtract a minute from the
    end of minute ranges, just a second. Here's the fixed code.

    #time_window.rb
    require 'runt_ext'

    module Runt

    #extends REWeek to allow for spanning across weeks
    class REWeek

    def initialize(start_day,end_day=6)
    @start_day = start_day
    @end_day = end_day
    end

    def include?(date)
    return true if @start_day==@end_day
    if @start_day < @end_day
    @start_day<=date.wday && @end_day>=date.wday
    else
    (@start_day<=date.wday && 6 >=date.wday) || (0 <=date.wday &&
    @end_day >=date.wday)
    end
    end

    end

    class StringParser < Runt::Intersect

    def initialize(string)
    super()
    add parsed(string)
    end

    #recursive method to parse input string
    def parse(token)
    case token
    when ""
    REWeek.new(0)
    when /^(.+);(.+)/ # split at semicolons
    parse($1) | parse($2)
    when /(\D+) (\d.+)/ # split days and times
    parse($1) & parse($2)
    when /(\D+) (\D+)/, /(\d+-\d+) (\d+-\d+)/ # split at spaces
    parse($1) | parse($2)
    when /([A-Z][a-z][a-z])-([A-Z][a-z][a-z])/ # create range of days
    REWeek.new(Runt.const_get($1), Runt.const_get($2))
    when /([A-Z][a-z][a-z])/ # create single day
    DIWeek.new(Runt.const_get($1))
    when /(\d\d)(\d\d)-(\d\d)(\d\d)/ #create time range
    start = Time.mktime(2000,1,1,$1.to_i,$2.to_i)
    # 0600-0900 should work like 0600-0859,
    stop = Time.mktime(2000,1,1,$3.to_i,$4.to_i) - 1
    REDay.new(start.hour, start.min, stop.hour, stop.min)
    end
    end
    alias :parsed :parse

    end

    end

    class TimeWindow < Runt::StringParser
    end
    Gordon Thiesfeld, Oct 22, 2007
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ruby Quiz

    [QUIZ] Animal Quiz (#15)

    Ruby Quiz, Jan 14, 2005, in forum: Ruby
    Replies:
    11
    Views:
    365
    James Edward Gray II
    Jan 18, 2005
  2. David Tran
    Replies:
    9
    Views:
    184
    David Tran
    Jan 21, 2005
  3. Ruby Quiz

    [QUIZ] 1-800-THE-QUIZ (#20)

    Ruby Quiz, Feb 18, 2005, in forum: Ruby
    Replies:
    15
    Views:
    323
    gabriele renzi
    Feb 24, 2005
  4. 7stud --
    Replies:
    1
    Views:
    89
    Morton Goldberg
    Sep 20, 2007
  5. Ruby Quiz

    [SUMMARY] Time Window (#144)

    Ruby Quiz, Oct 25, 2007, in forum: Ruby
    Replies:
    0
    Views:
    107
    Ruby Quiz
    Oct 25, 2007
Loading...

Share This Page