defmodule Cocktail.Span do
@moduledoc """
Struct used to represent a span of time.
It is composed of the following fields:
* from: the start time of the span
* until: the end time of the span
When expanding a `t:Cocktail.Schedule.t/0`, if it has a duration it will
produce a list of `t:t/0` instead of a list of `t:Cocktail.time/0`.
"""
@type t :: %__MODULE__{from: Cocktail.time(), until: Cocktail.time()}
@type span_compat :: %{:from => Cocktail.time(), :until => Cocktail.time(), optional(atom) => any}
@type overlap_mode ::
:contains
| :is_inside
| :is_before
| :is_after
| :is_equal_to
| :overlaps_the_start_of
| :overlaps_the_end_of
@enforce_keys [:from, :until]
defstruct from: nil,
until: nil
@doc """
Creates a new `t:t/0` from the given start time and end time.
## Examples
iex> new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
%Cocktail.Span{from: ~N[2017-01-01 06:00:00], until: ~N[2017-01-01 10:00:00]}
"""
@spec new(Cocktail.time(), Cocktail.time()) :: t
def new(from, until), do: %__MODULE__{from: from, until: until}
@doc """
Uses `Timex.compare/2` to determine which span comes first.
Compares `from` first, then, if equal, compares `until`.
## Examples
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> compare(span1, span2)
0
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 07:00:00], ~N[2017-01-01 12:00:00])
...> compare(span1, span2)
-1
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 07:00:00])
...> compare(span1, span2)
1
"""
@spec compare(span_compat, span_compat) :: Timex.Comparable.compare_result()
def compare(%{from: t, until: until1}, %{from: t, until: until2}), do: Timex.compare(until1, until2)
def compare(%{from: from1}, %{from: from2}), do: Timex.compare(from1, from2)
@doc """
Returns an `t:overlap_mode/0` to describe how the first span overlaps the second.
## Examples
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> overlap_mode(span1, span2)
:is_equal_to
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> span2 = new(~N[2017-01-01 07:00:00], ~N[2017-01-01 09:00:00])
...> overlap_mode(span1, span2)
:contains
iex> span1 = new(~N[2017-01-01 07:00:00], ~N[2017-01-01 09:00:00])
...> span2 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 10:00:00])
...> overlap_mode(span1, span2)
:is_inside
iex> span1 = new(~N[2017-01-01 06:00:00], ~N[2017-01-01 07:00:00])
...> span2 = new(~N[2017-01-01 09:00:00], ~N[2017-01-01 10:00:00])
...> overlap_mode(span1, span2)
:is_before
"""
@spec overlap_mode(span_compat, span_compat) :: overlap_mode
def overlap_mode(%{from: from, until: until}, %{from: from, until: until}), do: :is_equal_to
# credo:disable-for-next-line
def overlap_mode(%{from: from1, until: until1}, %{from: from2, until: until2}) do
from_comp = Timex.compare(from1, from2)
until_comp = Timex.compare(until1, until2)
cond do
from_comp <= 0 && until_comp >= 0 -> :contains
from_comp >= 0 && until_comp <= 0 -> :is_inside
Timex.compare(until1, from2) <= 0 -> :is_before
Timex.compare(from1, until2) >= 0 -> :is_after
from_comp < 0 && until_comp < 0 -> :overlaps_the_start_of
from_comp > 0 && until_comp > 0 -> :overlaps_the_end_of
end
end
end