recursionelixirreducetimex

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)

Solution

  • 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