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 and end_date but project's end date is later than report's end_date, accept
  • project's end date falls between report's start_date and end_date but project's start date is earlier than report's start_date, accept
  • project's start and end dates fall between report's start_date and end_date, accept (the current implementation)

  • report's start_date and end_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

2 Answers

0
Brett Beatty On Best Solutions

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
2
Aleksei Matiushkin On

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