【问题标题】:Build series of datetimes with exclusions in Elixir在 Elixir 中构建具有排除项的一系列日期时间
【发布时间】:2019-06-16 01:38:37
【问题描述】:

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

首先,有一个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 的时间段。

【问题讨论】:

    标签: datetime functional-programming elixir


    【解决方案1】:

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

    采取的步骤是:

    1. 构建适合 start_end 的所有可用 time_slots 的列表
    2. 删除与预订重叠的空档

    has_overlap? 检查有点棘手,可能需要更多测试。当 booking_start 位于该槽之前且 booking_end 位于该槽之后,它还会删除 eclipsed 槽。

    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]}
    ]
    

    【讨论】:

      猜你喜欢
      • 2022-01-13
      • 1970-01-01
      • 2021-12-19
      • 1970-01-01
      • 2013-06-20
      • 2018-09-16
      • 1970-01-01
      • 2014-12-27
      • 2022-01-13
      相关资源
      最近更新 更多