Reputation: 975
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)
Upvotes: 0
Views: 978
Reputation: 847
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>}
Upvotes: 1
Reputation: 1082
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
Upvotes: 1
Reputation: 120990
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>
Upvotes: 2