'Elixir - filter list of items with overlapping dates with a given date range
I need to refactor a function that filters a list of projects by a given date range:
defp filter_by_date_range(projects, %{start_date: start_date, end_date: end_date} = _report) do
projects
|> Enum.filter(&Date.compare(&1.start_date, start_date) in [:gt, :eq])
|> Enum.filter(&Date.compare(&1.end_date, end_date) in [:lt, :eq])
end
Right now, what it does is it returns the projects whose start dates and end dates fall in between the report's start_date
and end_date
. I want to modify it so that it will return projects as long there is an overlap of the dates. Example:
- project's start date falls between report's
start_date
andend_date
but project's end date is later than report'send_date
, accept - project's end date falls between report's
start_date
andend_date
but project's start date is earlier than report'sstart_date
, accept project's start and end dates fall between report's
start_date
andend_date
, accept (the current implementation)report's
start_date
andend_date
fall between project's start date and end date, accept- project's start and end dates are both earlier than report's
start_date
, reject - project's start and end dates are both later than report's
end_date
, reject
I came up with this nifty solution, but is there anything I can do to improve it?
defp filter_by_date_range(projects, %{start_date: start_date, end_date: end_date}) do
Enum.filter(projects, &do_dates_overlap?(&1, start_date, end_date))
end
defp do_dates_overlap?(project, start_date, end_date) do
cond do
Date.compare(project.end_date, start_date) == :lt -> false
Date.compare(project.start_date, end_date) == :gt -> false
true -> true
end
end
Solution 1:[1]
If you can safely make the assumption you'll never have a single-day report on the same day as a single-day project, you could just check that your comparisons are not equal:
defp do_dates_overlap?(project, start_date, end_date) do
Date.compare(project.end_date, start_date) != Date.compare(project.start_date, end_date)
end
If you can't make that assumption, you can just and
them together:
defp do_dates_overlap?(project, start_date, end_date) do
Date.compare(project.end_date, start_date) != :lt and Date.compare(project.start_date, end_date) != :gt
end
Solution 2:[2]
Simply join the filters with boolean OR
:
defp filter_by_date_range(projects, %{start_date: sd, end_date: ed}) do
Enum.filter(projects, &
Date.compare(&1.start_date, sd) in [:gt, :eq] or
Date.compare(&1.end_date, ed) in [:lt, :eq] or
(
Date.compare(&1.start_date, sd) == :lt and
Date.compare(&1.end_date, ed) == :gt
)
)
end
or, less explicit:
defp filter_by_date_range(projects, %{start_date: sd, end_date: ed}) do
Enum.filter(projects, & not(
Date.compare(&1.end_date, sd) == :lt or
Date.compare(&1.start_date, ed) == :gt
)
end
Solution 3:[3]
The easiest way to compare if two periods overlap (or intersect) is to check if the following condition is true:
p1_start <= p2_end && p2_start <= p1_end
For your case, there would be overlap (or intersection) if:
defp filter_by_date_range(projects, %{start_date: start_date, end_date: end_date} = _report) do
Enum.filter(projects, &(Date.compare(&1.start_date, end_date) != :gt and Date.compare(start_date, &1.end_date) != :gt))
end
It even works just as good for periods of one day.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Brett Beatty |
Solution 2 | |
Solution 3 |