Complex Range Reference: Determine Available Time Ranges Based on Schedules and Required Times
So I am trying to create a booking app and am running into some problems.
Basically I have a set of Assignments for a client:
appointments = [
Mon, 29 Dec 2014 11:00:00 PST -08:00..Mon, 29 Dec 2014 12:30:00 PST -08:00,
Mon, 29 Dec 2014 09:00:00 PST -08:00..Mon, 29 Dec 2014 10:30:00 PST -08:00,
Mon, 29 Dec 2014 14:00:00 PST -08:00..Mon, 29 Dec 2014 15:30:00 PST -08:00
]
and then I will book this time:
Mon, 29 Dec 2014 14:00:00 PST -08:00
who is looking for a proposed service that takes an hour and 30 minutes, so the time range I want to book is:
requested_time_range = Mon, 29 Dec 2014 14:00:00 PST -08:00..Mon, 29 Dec 2014 15:30:00 PST -08:00
and available time for the day:
full_time_range = Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 16:00:00 PST -08:00
Using the range_operators gem parameter ( https://github.com/monocle/range_operators ), I can do this:
a1 = full_time_range - appointments.first
which will result in (range operation -
will result in an array of ranges):
[
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 10:59:59 PST -08:00,
Mon, 29 Dec 2014 12:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00
]
so looping through all the appointments and subtracting them from full_time_range and then formatting the array so that we have a flat array, I come up with:
available_times = [
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 10:59:59 PST -08:00,
Mon, 29 Dec 2014 12:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00,
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 08:59:59 PST -08:00,
Mon, 29 Dec 2014 10:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00,
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 13:59:59 PST -08:00,
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 08:59:59 PST -08:00,
Mon, 29 Dec 2014 10:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00,
Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 13:59:59 PST -08:00
]
Now I cannot figure out how to find requested_time_range
in available_times
. In most cases, this is not true, because request_time_range is sometimes inside one element of the available_times array, sometimes not.
Is there an exact way to find out if it is available requested_time_range
in my case? (I think I need to do something with accessible_names, but I don't know what
source to share
You need to change request_time_range to max
be 1 second (or more) less than currently.
booking_time = some_time # "Mon, 29 Dec 2014 14:00:00 PST -08:00" in your case
# 5399.seconds equals (1.5.hours - 1.second )
request_time_range = booking_time..(booking_time + 5399.seconds)
Then the work should :
available_times.any? do |time_range|
(time_range.min <= requested_time_range.min) &&
(time_range.max >= requested_time_range.max)
end
source to share
I had fun doing it my own way ... it may or may not be for you, and there is room for improvement in speed, but here's what I came up with. You can integrate it into your situation however you want.
EDIT: I also haven't tested this for all scenarios ...
range_comparison.rb
####
## Use case: Are you available between Nov 19th and 30th?
## No, I'm only available between Dec 1st and 2nd
##
## requested_dates = DateTime.parse("Nov 19, 2014")..DateTime.parse("Nov 30, 2014")
## available_dates = DateTime.parse("Dec 1, 2014")..DateTime.parse("Dec 2, 2014")
## p 'Dates intersect on: '
## p requested_dates.first_coincide_with(available_dates)
####
class Range
def first_coincide_with(compared_range)
# Barf if it anything other than a Range
raise(ArgumentError, 'Only Ranges are permitted!') unless compared_range.kind_of?(Range)
# Find the closest starting point for searching
min = [self.min, compared_range.min].max
# Find the end time
max = [self.max, compared_range.max].min
# Quickly exit if they are completely out of bounds to one another
return if min > max
# Now start searching for first available unit
# Hopefully they know to use this in a strictly Integer Range... :)
pos = min
until pos > max
return pos if pos >= min
pos += 1
end
nil
end
end
source to share
The problem is that the assignment is not comprehensive. End times should be excluded, so when plotting ranges use ...
instead ..
.
Imagine that you also have 9 to 10:30
a 10:30 to 12
. With the ranges included, you end up with a graph that looks like this:
|------|
|------|
Instead of excluding the last value:
|------|------
Without overlap, when checking that the range array does not contain a new range, ensure that no range contains the beginning or end of a new range.
def include?(ranges, range)
ranges.any?{ |r| r.cover?(range.begin) || r.cover?(range.end) }
end
def valid?(appointments, appointment)
!include?(appointments, appointment)
end
Note the difference between Range # include? and Range Cover #? ... You want cover?
.
EDIT . To find available times, sort the ranges and take non-zero gaps.
def available_ranges(ranges)
pairs = ranges.sort_by(&:begin).each_cons(2)
new_ranges = pairs.map do |r1, r2|
r1.end...r2.begin
end
new_ranges.reject{ |r| r.begin == r.end }
end
source to share