On 10/19/07, Ruby Quiz <james / grayproductions.net> 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.