I assume that we are given hour ranges for 'plan', 'available' and 'coverage', where 'coverage' is the 'available` range preceded and followed by 'stand-by' ranges. Moreover, 'coverage' contains 'plan' and either or both of its 'stand-by' ranges may be of zero duration.
Code
def categories(avail, plan)
adj_avail = adj_avail(avail)
adj_plan = adj_plan(avail, plan)
arr = []
finish = [adj_avail[:available][:start], adj_plan[:start]].min
add_block(arr, :standby, adj_avail[:coverage][:start], finish)
start = finish
finish = [adj_avail[:available][:finish], adj_plan[:start]].min
add_block(arr, :available, start, finish)
start = finish
add_block(arr, :standby, finish, adj_plan[:start])
arr << [:plan, adj_plan]
finish = [adj_plan[:finish], adj_avail[:available][:finish]].max
add_block(arr, :available, adj_plan[:finish], finish)
add_block(arr, :standby, finish, adj_avail[:coverage][:finish])
restore_times(arr)
end
def adj_avail(avail)
avail.each_with_object({}) do |(k,g),h|
start, finish = g[:start], g[:finish]
h[k] = case k
when :coverage
{ start: start, finish: finish + (finish < start ? 24 : 0) }
else # when :available
{ start: start + (start < h[:coverage][:start] ? 24 : 0),
finish: finish + (finish < start ? 24 : 0) }
end
end
end
def adj_plan(avail, plan)
{ start: plan[:start] + (plan[:start] < avail[:coverage][:start] ? 24 : 0),
finish: plan[:finish] + (plan[:finish] < plan[:start] ? 24 : 0) }
end
def add_block(arr, value, curr_epoch, nxt_epoch)
arr << [value, { start: curr_epoch, finish: nxt_epoch }] if nxt_epoch > curr_epoch
end
def restore_times(arr)
arr.map! do |k,g|
start, finish = g.values_at(:start, :finish)
start -= 24 if start > 24
finish -= 24 if finish > 24
[k, { start: start, finish: finish }]
end
end
Examples
avail = { coverage: { start: 3, finish: 18 },
available: { start: 3, finish: 12 } }
plan = { start: 6, finish: 15 }
categories(avail, plan)
#=> [[:available, {:start=>3, :finish=>6} ],
# [:plan, {:start=>6, :finish=>15} ],
# [:standby, {:start=>15, :finish=>18}]]
avail = { coverage: { start: 22, finish: 11 },
available: { start: 23, finish: 10 } }
plan = { start: 24, finish: 9 }
categories(avail, plan)
#=> [[:standby, {:start=>22, :finish=>23}],
# [:available, {:start=>23, :finish=>24}],
# [:plan, {:start=>24, :finish=>9 }],
# [:available, {:start=>9, :finish=>10}],
# [:standby, {:start=>10, :finish=>11}]]
avail = { coverage: { start: 1, finish: 13 },
available: { start: 2, finish: 3 } }
plan = { start: 4, finish: 12 }
categories(avail, plan)
#=> [[:standby, {:start=>1, :finish=>2 }],
# [:available, {:start=>2, :finish=>3 }],
# [:standby, {:start=>3, :finish=>4 }],
# [:plan, {:start=>4, :finish=>12}],
# [:standby, {:start=>12, :finish=>13}]]
Explanation
The main complication here is when the finish hour for 'coverage' is less than the start hour, meaning that the 'coverage' range contains midnight. When this occurs, the 'available' and 'plan' ranges may also contain midnight. I've dealt with this by adding 24 hours to hours after midnight before computing the ranges and then subtracting 24 hours from all hour values that exceed 24 after computing the ranges.
Consider the second example above.
avail = { coverage: { start: 22, finish: 11 },
available: { start: 23, finish: 10 } }
adj_avail(avail)
#=> {:coverage=> {:start=>22, :finish=>35},
# :available=>{:start=>23, :finish=>34}}
plan = { start: 24, finish: 9 }
adj_plan(avail, plan)
#=> {:start=>24, :finish=>33}
If I execute categories
for these values of avail
and plan
, with the last line commented out, I obtain
a = categories(avail, plan)
#=> [[:standby, {:start=>22, :finish=>23}],
# [:available, {:start=>23, :finish=>24}],
# [:plan, {:start=>24, :finish=>33}],
# [:available, {:start=>33, :finish=>34}],
# [:standby, {:start=>34, :finish=>35}]]
and
restore_times(a)
#=> the return value shown above