简体   繁体   中英

Elixir reduce to find first available date in range with Timex

I have a list of dates that represent bookings for a room. I want to be able to find out where the first available day is in the date range.

# let us assume today is the 1st Feb
today = #DateTime<2019-02-01 00:00:00Z>

# here are our booked days
booked_days = [
  #DateTime<2019-02-08 00:00:00Z>, 
  #DateTime<2019-02-05 00:00:00Z>, 
  #DateTime<2019-02-03 00:00:00Z>, 
  #DateTime<2019-02-02 00:00:00Z>
]

What we'd like to have returned here is #DateTime<2019-02-04 00:00:00Z> because it's the first available date.

I've looked at doing something like this using Enum.reduce_while in combination with Timex.Interval , but with no luck as reduce_while seems to return the interval after the first call.

today = Timex.now() |> Timex.beginning_of_day()

first_available =
  Enum.reduce_while(booked_days, today, fn from, until ->
    interval = Timex.Interval.new(from: from, until: until)
    duration = Timex.Interval.duration(interval, :days)

    if duration <= 1,
      do: {:cont, from},
      else: {:halt, interval}
  end)

Although the answer by @Badu is correct, I'd post the solution with the desired Enum.reduce_while/3 .

Elixir nowadays has great built-in support for dates in the first place, so I doubt I follow why would you need Timex . And you'd better deal with dates not with date times when it comes to booked days (unless you allow pay-per-hour bookings.) But if you want DateTime s, here you go:

# Please next time make your input available to copy-paste
[today | booked_days] =
  [1, 8, 5, 3, 2]
  |> Enum.map(& {{2019, 02, &1}, {0, 0, 0}}
  |> NaiveDateTime.from_erl!()
  |> DateTime.from_naive!("Etc/UTC"))

booked_days
|> Enum.sort(& Date.compare(&1, &2) == :lt)
|> Enum.reduce_while(today, fn d, curr ->
  if Date.diff(d, curr) == 1,
    do: {:cont, d},
    else: {:halt, DateTime.add(curr, 3600 * 24)}
end)
#⇒ #DateTime<2019-02-04 00:00:00Z>

First, you can sort the dates in ascending order. Then iterate the dates and check for empty intervals between dates and return the date if the date is greater than or equal to from date.

sorted_dates = Enum.sort(booked_days , fn a, b -> Timex.compare(a, b, :days)<0 end) 
get_next_booking_date(sorted_dates, today)
def get_next_booking_date([], _from_date) do
    nil
end

def get_next_booking_date([_last_booking_date], _from_date) do
   # You can add a day  to last booking date and return that date or return nil depending on your need
   # Timex.add(_last_booking_date, Timex.Duration.from_days(1))
   nil
end

def get_next_booking_date([next, next2 | rest], from_date) do

    # add a day to the current day and check if there's an empty interval and that the empty slot is greater than from date

    temp_next = Timex.add(next, Timex.Duration.from_days(1))        

    if Timex.compare(temp_next, next2, :days) == -1  and Timex.compare(temp_next, from_date) >= 0 do
      temp_next
    else
      get_next_booking_date([next2 | rest], from)
    end
end

Heres a version without Timex

Create an array of elements with [[1st date, 2nd date], [2nd date, 3rd date], .. [ith, (i-1)st]...] (using zip by offset 1) then find the position where the two differ by more than 1 day.

defmodule DateGetter do

  def get_next_date(booked_dates) do

    sorted = Enum.sort(booked_dates)

    date = sorted
    |> Enum.zip(Enum.drop sorted, 1) # or use Enum.chunk_every for large sizes
    |> Enum.find(fn {d1, d2} -> DateTime.diff(d2, d1) > 86400 end)

    case date do
      nil ->
        {:error, "no dates found"}
      {date1, date2} ->
        (DateTime.to_unix(date1) + 86400) |> DateTime.from_unix
    end
  end
end

# Sample input:
booked_dates =
  [2,5,3,8]
  |> Enum.map(fn d ->
    DateTime.from_iso8601("2015-01-0#{d} 01:00:00Z")
    |> elem(1)
  end)   

DateGetter.get_next_date booked_dates
#> {:ok, #DateTime<2015-01-04 01:00:00Z>}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM