在 Elixir 中构建一系列包含排除项的日期时间



我想在删除预订的时间段的同时生成两个日期之间的可用时间段列表。

首先,有一个time_slots列表,这些是{start_time, end_time}元组,可以在任何给定的日期预订:

time_slots = [
{~T[09:00:00], ~T[13:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[17:00:00], ~T[21:00:00]}
]

然后有一个bookings列表,其中包含{booking_start, booking_end}元组:

bookings = [
{~N[2019-06-14 09:00:00Z], ~N[2019-06-14 17:00:00Z]},
{~N[2019-06-15 09:00:00Z], ~N[2019-06-15 13:00:00Z]},
# some bookings may sit outside a slot range
{~N[2019-06-16 15:00:00Z], ~N[2019-06-16 21:00:00Z]}
]

还有一个元组包含我们希望在其之间生成所有可用时隙的{start_date, end_date}

start_end = {~N[2019-06-13 01:00:00Z], ~N[2019-06-16 23:00:00Z]}

在这种情况下,我们需要生成所有可用的时隙,并返回:

available_slots = [
{~N[2019-06-13 09:00:00Z], ~N[2019-06-13 13:00:00Z]},
{~N[2019-06-13 13:00:00Z], ~N[2019-06-13 17:00:00Z]},
{~N[2019-06-13 17:00:00Z], ~N[2019-06-13 21:00:00Z]},
{~N[2019-06-14 17:00:00Z], ~N[2019-06-14 21:00:00Z]},
{~N[2019-06-15 13:00:00Z], ~N[2019-06-15 17:00:00Z]},
{~N[2019-06-15 17:00:00Z], ~N[2019-06-15 21:00:00Z]},
{~N[2019-06-16 09:00:00Z], ~N[2019-06-16 13:00:00Z]}
]
  • 对于要占用的时间段,需要预订的开始或结束在其内部重叠(无论重叠有多小): 例如,0900–1000 的预订将填补 0900–1300、0900–
    • 1700 和 0900–2100 的时间段
  • 一个时间段可以填满多个预订: 例如,我们可以预订 0900–1000 和 1000–
    • 1200,它们都适合 0900–1300 时间段。

这是一个可能的解决方案,将代码粘贴到文件timeslots.exs中,然后使用elixir timeslots.exs运行。

采取的步骤是:

  1. 列出适合start_end的所有可用time_slots
  2. 删除与预订重叠的时段

has_overlap?检查有点棘手,可能需要更多测试。当booking_start在插槽之前,booking_end在插槽之后时,它还会删除日食插槽。

defmodule TimeSlots do
def available_time_slots(bookings, time_slots, start_end) do
time_slots_in_range(time_slots, start_end)
|> remove_booked_slots(bookings)
end
# Build a list with all time_slots between start_date_time and end_date_time
defp time_slots_in_range(time_slots, {start_date_time, end_date_time}) do
start_date = NaiveDateTime.to_date(start_date_time)
end_date = NaiveDateTime.to_date(end_date_time)
Date.range(start_date, end_date)
|> Enum.map(fn date -> daily_time_slots(date, time_slots) end)
|> List.flatten
|> Enum.filter(fn {slot_start_date_time, slot_end_date_time} ->
NaiveDateTime.compare(start_date_time, slot_start_date_time) != :gt &&
NaiveDateTime.compare(end_date_time, slot_end_date_time) != :lt
end)
end
defp daily_time_slots(date, time_slots) do
Enum.map(time_slots, &(create_time_slot(date, &1)))
end
defp create_time_slot(date, {start_time, end_time}) do
{:ok, start_date_time} = NaiveDateTime.new(date, start_time)
{:ok, end_date_time} = NaiveDateTime.new(date, end_time)
{start_date_time, end_date_time}
end
defp remove_booked_slots(time_slots, bookings) do
Enum.reject(time_slots, fn time_slot ->
Enum.reduce(bookings, false, fn booking, acc ->
acc or has_overlap?(booking, time_slot)
end)
end)
end
# (slot_start <= booking_start < slot_end)
# or (slot_start < booking_end <= slot_end)
# or (booking_start <= slot_start and slot_end <= booking_end)
defp has_overlap?({booking_start, booking_end}, {slot_start, slot_end}) do
(NaiveDateTime.compare(slot_start, booking_start) != :gt &&
NaiveDateTime.compare(booking_start, slot_end) == :lt) ||
(NaiveDateTime.compare(slot_start, booking_end) == :lt &&
NaiveDateTime.compare(booking_end, slot_end) != :gt) ||
(NaiveDateTime.compare(booking_start, slot_start) != :gt &&
NaiveDateTime.compare(slot_end, booking_end) != :gt)
end
end

time_slots = [
{~T[09:00:00], ~T[13:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[17:00:00], ~T[21:00:00]}
]
bookings = [
{~N[2019-06-14 09:00:00Z], ~N[2019-06-14 17:00:00Z]},
{~N[2019-06-15 09:00:00Z], ~N[2019-06-15 13:00:00Z]},
# some bookings may sit outside a slot range
{~N[2019-06-16 15:00:00Z], ~N[2019-06-16 21:00:00Z]}
]
# I've changed the end date to 2019-06-16 to match the expected result
start_end = {~N[2019-06-13 01:00:00Z], ~N[2019-06-16 23:00:00Z]}
available_slots = [
{~N[2019-06-13 09:00:00Z], ~N[2019-06-13 13:00:00Z]},
{~N[2019-06-13 13:00:00Z], ~N[2019-06-13 17:00:00Z]},
{~N[2019-06-13 17:00:00Z], ~N[2019-06-13 21:00:00Z]},
{~N[2019-06-14 17:00:00Z], ~N[2019-06-14 21:00:00Z]},
{~N[2019-06-15 13:00:00Z], ~N[2019-06-15 17:00:00Z]},
{~N[2019-06-15 17:00:00Z], ~N[2019-06-15 21:00:00Z]},
{~N[2019-06-16 09:00:00Z], ~N[2019-06-16 13:00:00Z]}
]
# Test it
IO.inspect TimeSlots.available_time_slots(bookings, time_slots, start_end)

应该给出正确的结果:

[
{~N[2019-06-13 09:00:00], ~N[2019-06-13 13:00:00]},
{~N[2019-06-13 13:00:00], ~N[2019-06-13 17:00:00]},
{~N[2019-06-13 17:00:00], ~N[2019-06-13 21:00:00]},
{~N[2019-06-14 17:00:00], ~N[2019-06-14 21:00:00]},
{~N[2019-06-15 13:00:00], ~N[2019-06-15 17:00:00]},
{~N[2019-06-15 17:00:00], ~N[2019-06-15 21:00:00]},
{~N[2019-06-16 09:00:00], ~N[2019-06-16 13:00:00]}
]

最新更新