Packages
timex
1.0.0-rc1
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/date/interval.ex
defmodule Timex.Interval do
@moduledoc """
"""
alias Timex.Date
alias Timex.DateTime
alias Timex.DateFormat
alias Timex.Interval
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
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 `Date.shift`, so the options provided should match it's valid options.
## Examples
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 22}), until: Date.from({2014, 9, 29}))
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-29)"
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 22}), until: [days: 7])
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-29)"
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 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: Date.from({{2014, 9, 22}, {15, 30, 0}}), until: [mins: 20], right_open: false)
...> |> Interval.format!("%H:%M", :strftime)
"[15:30, 15:50]"
"""
def new(options \\ []) do
from = Keyword.get(options, :from, Date.now())
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
x when is_list(x) -> Date.shift(from, x)
x -> x
end
%Interval{from: from, until: until,
left_open: left_open, right_open: right_open,
step: step}
end
@doc """
Return the interval duration, given a unit.
When the unit is one of `:secs`, `:mins`, `:hours`, `:days`, `:weeks`, `:months`, `:years`, the result is an `integer`.
When the unit is `:timestamp`, the result is a tuple representing a valid `Timex.Time`.
## Example
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 22}), until: [months: 5])
...> |> Interval.duration(:months)
5
iex> use Timex
...> Interval.new(from: Date.from({{2014, 9, 22}, {15, 30, 0}}), until: [mins: 20])
...> |> Interval.duration(:timestamp)
{0, 1200, 0}
"""
def duration(%Interval{from: from, until: until}, unit) do
Date.diff(from, until, 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: Date.from({2014, 9, 22}), until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 1]) |> Enum.map(&DateFormat.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-23", "2014-09-24", "2014-09-25"]
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 22}), until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 2]) |> Enum.map(&DateFormat.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-24"]
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 22}), until: [days: 3], right_open: false)
...> |> Interval.with_step([days: 3]) |> Enum.map(&DateFormat.format!(&1, "%Y-%m-%d", :strftime))
["2014-09-22", "2014-09-25"]
"""
def with_step(%Interval{} = interval, step) do
%Interval{interval | :step => step}
end
@doc """
Formats the interval as a human readable string.
## Examples
iex> use Timex
...> Interval.new(from: Date.from({2014, 9, 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: Date.from({2014, 9, 22}), until: [days: 3])
...> |> Interval.format!("%Y-%m-%d", :strftime)
"[2014-09-22, 2014-09-25)"
"""
def format(%Interval{} = interval, format, formatter \\ nil) do
case DateFormat.format(interval.from, format, formatter) do
{:error, _} = err -> err
{:ok, from} ->
case DateFormat.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!(%Interval{} = 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, for: Interval 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?(%Interval{from: from, until: until}, %DateTime{} = value) do
# Just tests for set membership (date is within the provided (inclusive) range)
result = cond do
Date.compare(value, from) < 1 -> false
Date.compare(value, until) > 0 -> false
:else -> true
end
{:ok, result}
end
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
next_date = Date.shift(current_date, keywords)
do_reduce({next_date, end_date, right_open, keywords}, fun.(current_date, acc), fun)
end
end
defp get_starting_date(%Interval{from: from, step: step, left_open: true}), do: Date.shift(from, step)
defp get_starting_date(%Interval{from: from}), do: from
defp has_recursion_ended?(current_date, end_date, true), do: Date.compare(end_date, current_date) < 1
defp has_recursion_ended?(current_date, end_date, false), do: Date.compare(end_date, current_date) < 0
end
end