Packages
timex
3.1.21
3.7.13
3.7.12
3.7.11
3.7.9
3.7.8
3.7.7
3.7.6
3.7.5
3.7.3
3.7.2
3.7.1
3.7.0
3.6.4
3.6.3
3.6.2
retired
3.6.1
3.6.0
3.5.0
3.4.2
3.4.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.25
retired
3.1.24
3.1.23
3.1.22
3.1.21
3.1.20
3.1.19
3.1.18
3.1.17
3.1.16
3.1.15
3.1.13
3.1.12
3.1.11
3.1.10
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.2.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.0
1.0.2
1.0.1
1.0.0
1.0.0-rc4
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
1.0.0-pre
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.1
0.6.0
0.5.0
0.4.8
0.4.7
0.4.6
Timex is a rich, comprehensive Date/Time library for Elixir projects, with full timezone support via the :tzdata package. If you need to manipulate dates, times, datetimes, timestamps, etc., then Timex is for you!
Current section
Files
Jump to
Current section
Files
lib/interval/interval.ex
defmodule Timex.Interval do
@moduledoc """
This module is used for creating and manipulating DateTime intervals.
"""
alias Timex.Duration
defmodule FormatError do
@moduledoc """
Thrown when an error occurs with formatting an Interval
"""
defexception message: "Unable to format interval!"
def exception([message: message]) do
%FormatError{message: message}
end
end
@type t :: %__MODULE__{}
@type valid_interval_step :: [microseconds: integer()] |
[milliseconds: integer()] |
[seconds: integer()] |
[minutes: integer()] |
[hours: integer()] |
[days: integer()] |
[weeks: integer()]
@enforce_keys [:from, :until]
defstruct from: nil,
until: nil,
left_open: false,
right_open: true,
step: [days: 1]
@doc """
Create a new Interval struct.
Note: By default intervals are right open.
Valid keywords:
- `from`: The date the interval starts at. Should be a DateTime.
- `until`: Either a DateTime, or a time shift that will be applied to the `from` date.
- `left_open`: Whether the interval is left open. See explanation below.
- `right_open`: Whether the interval is right open. See explanation below.
- `step`: The step to use when iterating the interval, defaults to `[days: 1]`
The terms`left_open` and `right_open` come from the mathematical concept of intervals, the following
excerpt from Wikipedia gives a good explanation of their meaning:
"An interval is said to be left-open if and only if it has no minimum
(an element that is smaller than all other elements); right-open if it has no maximum;
and open if it has both properties. The interval [0,1) = {x | 0 ≤ x < 1}, for example,
is left-closed and right-open. The empty set and the set of all reals are open intervals,
while the set of non-negative reals, for example, is a right-open but not left-open interval.
The open intervals coincide with the open sets of the real line in its standard topology."
Note: `until` shifts delegate to `Timex.shift`, so the options provided should match its valid options.
## Examples
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: ~D[2014-09-29])
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-29)"
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 7])
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-29)"
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 7], left_open: true, right_open: false)
...> |> Interval.format!("%Y-%m-%d", :strftime)
"(2014-09-22, 2014-09-29]"
iex> use Timex
...> Interval.new(from: ~N[2014-09-22T15:30:00], until: [minutes: 20], right_open: false)
...> |> Interval.format!("%H:%M", :strftime)
"[15:30, 15:50]"
"""
@spec new(Keyword.t) :: Interval.t | {:error, :invalid_until} | {:error, :invalid_step}
def new(options \\ []) do
from = case Keyword.get(options, :from) do
nil -> Timex.Protocol.NaiveDateTime.now()
%NaiveDateTime{} = d -> d
d -> Timex.to_naive_datetime(d)
end
left_open = Keyword.get(options, :left_open, false)
right_open = Keyword.get(options, :right_open, true)
step = Keyword.get(options, :step, [days: 1])
until = case Keyword.get(options, :until, [days: 1]) do
{:error, _} = err -> err
x when is_list(x) -> Timex.shift(from, x)
%NaiveDateTime{} = d -> d
%DateTime{} = d -> Timex.to_naive_datetime(d)
%Date{} = d -> Timex.to_naive_datetime(d)
_ -> {:error, :invalid_until}
end
attrs = %{from: from,
until: until,
left_open: left_open,
right_open: right_open,
step: valid_step_or_error(step)}
build_struct_or_error(attrs)
end
@spec build_struct_or_error(map()) :: Interval.t | {:error, atom()}
defp build_struct_or_error(%{until: err = {:error, _}}), do: err
defp build_struct_or_error(%{step: err = {:error, _}}), do: err
defp build_struct_or_error(valid_attrs), do: struct(__MODULE__, valid_attrs)
@spec valid_step_or_error(any()) :: valid_interval_step | {:error, :invalid_step}
defp valid_step_or_error(step = [milliseconds: _]), do: step
defp valid_step_or_error(step = [microseconds: _]), do: step
defp valid_step_or_error(step = [seconds: _]), do: step
defp valid_step_or_error(step = [minutes: _]), do: step
defp valid_step_or_error(step = [hours: _]), do: step
defp valid_step_or_error(step = [days: _]), do: step
defp valid_step_or_error(step = [weeks: _]), do: step
defp valid_step_or_error(_), do: {:error, :invalid_step}
@doc """
Return the interval duration, given a unit.
When the unit is one of `:seconds`, `:minutes`, `:hours`, `:days`, `:weeks`, `:months`, `:years`, the result is an `integer`.
When the unit is `:duration`, the result is a `Duration` struct.
## Example
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [months: 5])
...> |> Interval.duration(:months)
5
iex> use Timex
...> Interval.new(from: ~N[2014-09-22T15:30:00], until: [minutes: 20])
...> |> Interval.duration(:duration)
Duration.from_minutes(20)
"""
def duration(%__MODULE__{from: from, until: until}, :duration) do
Timex.diff(until, from, :microseconds) |> Duration.from_microseconds
end
def duration(%__MODULE__{from: from, until: until}, unit) do
Timex.diff(until, from, unit)
end
@doc """
Change the step value for the provided interval.
The step should be a keyword list valid for use with `Timex.Date.shift`.
## Examples
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 1]) |> Enum.map(&Timex.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-23", "2014-09-24", "2014-09-25"]
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 2]) |> Enum.map(&Timex.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-24"]
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 3]) |> Enum.map(&Timex.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-25"]
"""
@spec with_step(Interval.t, any()) :: Interval.t | {:error, :invalid_step}
def with_step(%__MODULE__{} = interval, step) do
case valid_step_or_error(step) do
error = {:error, _} -> error
step -> %__MODULE__{interval | step: step}
end
end
@doc """
Formats the interval as a human readable string.
## Examples
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 3])
...> |> Interval.format!("%Y-%m-%d %H:%M", :strftime)
"[2014-09-22 00:00, 2014-09-25 00:00)"
iex> use Timex
...> Interval.new(from: ~D[2014-09-22], until: [days: 3])
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-25)"
"""
def format(%__MODULE__{} = interval, format, formatter \\ nil) do
case Timex.format(interval.from, format, formatter) do
{:error, _} = err -> err
{:ok, from} ->
case Timex.format(interval.until, format, formatter) do
{:error, _} = err -> err
{:ok, until} ->
lopen = if interval.left_open, do: "(", else: "["
ropen = if interval.right_open, do: ")", else: "]"
{:ok, "#{lopen}#{from}, #{until}#{ropen}"}
end
end
end
@doc """
Same as `format/3`, but raises a `Timex.Interval.FormatError` on failure.
"""
def format!(%__MODULE__{} = interval, format, formatter \\ nil) do
case format(interval, format, formatter) do
{:ok, str} -> str
{:error, e} -> raise FormatError, message: "#{inspect e}"
end
end
defimpl Enumerable do
def reduce(interval, acc, fun) do
do_reduce({get_starting_date(interval), interval.until, interval.right_open, interval.step}, acc, fun)
end
def member?(%Timex.Interval{} = interval, value) do
result = cond do
before?(interval, value) -> false
after?(interval, value) -> false
:else -> true
end
{:ok, result}
end
defp before?(%Timex.Interval{from: from, left_open: true}, value), do: Timex.compare(value, from) <= 0
defp before?(%Timex.Interval{from: from, left_open: false}, value), do: Timex.compare(value, from) < 0
defp after?(%Timex.Interval{until: until, right_open: true}, value), do: Timex.compare(value, until) >= 0
defp after?(%Timex.Interval{until: until, right_open: false}, value), do: Timex.compare(value, until) > 0
def count(_interval) do
{:error, __MODULE__}
end
defp do_reduce(_state, {:halt, acc}, _fun), do: {:halted, acc}
defp do_reduce( state, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(state, &1, fun)}
defp do_reduce({current_date, end_date, right_open, keywords}, {:cont, acc}, fun) do
if has_recursion_ended?(current_date, end_date, right_open) do
{:done, acc}
else
case Timex.shift(current_date, keywords) do
{:error, {:unknown_shift_unit, _}} ->
raise FormatError, message: "Invalid step unit for %Timex.Interval{}"
{:error, e} ->
raise FormatError, message: "Timex.shift error during reduce of %Timex.Interval{}: #{inspect e}"
next_date ->
do_reduce({next_date, end_date, right_open, keywords}, fun.(current_date, acc), fun)
end
end
end
defp get_starting_date(%Timex.Interval{from: from, step: step, left_open: true}), do: Timex.shift(from, step)
defp get_starting_date(%Timex.Interval{from: from}), do: from
defp has_recursion_ended?(current_date, end_date, true), do: Timex.compare(end_date, current_date) < 1
defp has_recursion_ended?(current_date, end_date, false), do: Timex.compare(end_date, current_date) < 0
end
end