Skip to content

Instantly share code, notes, and snippets.

@jcsjcs
Created June 21, 2010 09:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcsjcs/3b2e5310231f36353707 to your computer and use it in GitHub Desktop.
Save jcsjcs/3b2e5310231f36353707 to your computer and use it in GitHub Desktop.
BusinessHours challenge
# business_hours.rb
#
# 2010-06-30
# ----------
# initialize, update and closed methods on BusinessHours
# simplified after the challenge following comments from Ryan Bates.
require 'time'
# Helpers on the Time class
class Time
# Key to the DayRules - day abbreviation as a symbol.
def key_by_day
[:sun, :mon, :tue, :wed, :thu, :fri, :sat][self.wday]
end
# Key to the DayRules - date as YYYY-MM-DD.
def key_by_date
self.strftime('%Y-%m-%d')
end
# Start of the next day.
def next_day_at_midnight
Time.parse(self.strftime('%Y-%m-%d')) + (24*60*60)
end
# Change the time.
def set_time_to(time)
Time.local(self.year, self.month, self.day, time.hour, time.min)
end
end
# The rules for a day: closed?, opening time and closing time.
class DayRule
attr_accessor :opening_time, :closing_time, :is_closed
def initialize(opening_time, closing_time, is_closed=false)
@opening_time = Time.parse(opening_time)
@closing_time = Time.parse(closing_time)
@is_closed = is_closed
end
# Number of seconds from the given time until closing time.
def seconds_in_day_from(time)
if @is_closed || time > time.set_time_to( @closing_time )
0
else
time.set_time_to( @closing_time ) - start_time(time)
end
end
# Bring a time forward to the opening time if it is earlier.
def start_time(time)
if time < time.set_time_to( @opening_time )
time.set_time_to( @opening_time )
else
time
end
end
end
# Store the business hours rules and calculate the deadline.
class BusinessHours
def initialize(opening_time, closing_time)
@day_rules = {}
@day_rules[:default] = DayRule.new(opening_time, closing_time)
end
def update(day_or_date, opening_time, closing_time)
@day_rules[make_date_key(day_or_date)] = DayRule.new(opening_time, closing_time)
end
def closed(*closed_days)
closed_days.each do |closed_on|
@day_rules[make_date_key(closed_on)] = DayRule.new("00:00 AM", "00:00 AM", true)
end
end
def calculate_deadline(seconds, from_time)
this_day = Time.parse(from_time)
while seconds > 0 do
if seconds < day_rule(this_day).seconds_in_day_from(this_day)
this_day = day_rule(this_day).start_time(this_day) + seconds
seconds = 0
else
seconds -= day_rule(this_day).seconds_in_day_from(this_day)
this_day = this_day.next_day_at_midnight
end
end
this_day
end
private
# Find the matching day rule. First check using the specific date as those rules
# have precedence over the day of the week rules. Use the default rule when there's no match.
def day_rule(for_day)
@day_rules[for_day.key_by_date] || @day_rules[for_day.key_by_day] || @day_rules[:default]
end
# Make sure the keys to be used in seeking the day rules are consistent.
# This ensures that <tt>hours.update "Dec 24, 2010", "8:00 AM", "1:00 PM"</tt>
# and <tt>hours.update "2010-12-24", "8:00 AM", "1:00 PM"</tt> do the same thing.
def make_date_key(day_or_date)
if day_or_date.class == Symbol
day_or_date
else
Time.parse(day_or_date).key_by_date
end
end
end
if $0 == __FILE__
require 'test/unit'
# Tests
class BusinessHoursTest < Test::Unit::TestCase
def setup
@hours = BusinessHours.new("9:00 AM", "3:00 PM")
@hours.update :fri, "10:00 AM", "5:00 PM"
@hours.closed :sun, :wed, "Dec 25, 2010"
end
def test_basic
assert_equal(Time.parse("Mon Jun 07 11:10:00 2010"), @hours.calculate_deadline(2*60*60, "Jun 7, 2010 9:10 AM"))
end
def test_late
assert_equal(Time.parse("Thu Jun 10 09:03:00 2010"), @hours.calculate_deadline(15*60, "Jun 8, 2010 2:48 PM"))
end
def test_holiday
@hours.update "Dec 24, 2010", "8:00 AM", "1:00 PM"
assert_equal(Time.parse("Mon Dec 27 11:00:00 2010"), @hours.calculate_deadline(7*60*60, "Dec 24, 2010 6:45 AM"))
end
def test_holiday_alternate
@hours.update "2010-12-24", "8:00 AM", "1:00 PM"
assert_equal(Time.parse("Mon Dec 27 11:00:00 2010"), @hours.calculate_deadline(7*60*60, "Dec 24, 2010 6:45 AM"))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment