Current section
Files
Jump to
Current section
Files
lib/duration.ex
defmodule Duration do
@type t :: %__MODULE__{
years: non_neg_integer,
months: pos_integer,
days: pos_integer,
hours: non_neg_integer,
minutes: non_neg_integer,
seconds: non_neg_integer
}
defstruct years: 0,
months: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0
alias Duration.Parser
@moduledoc """
Convenient module to play with ISO 8601:2004 durations and `Timex.shift/2`.
"""
@doc """
Loads a `Duration.t`.
"""
@spec new(Duration.t) :: {:ok, Duration.t} | {:error, atom}
@spec new(String.t) :: {:ok, Duration.t} | {:error, atom}
@spec new(list) :: {:ok, Duration.t} | {:error, atom}
def new(%Duration{} = val), do: {:ok, val}
def new(val) when is_binary(val) do
parse(val)
end
def new(opts) when is_list(opts) do
params = opts |> Keyword.take([:years, :months, :days, :hours, :minutes, :seconds])
params = params |> Enum.map(fn {k, v} -> {k, abs(v)} end)
if params == opts do
{:ok, struct(__MODULE__, params)}
else
{:error, :wrong_options}
end
end
@doc """
Parse a duration string to a `Duration.t`.
## Examples
iex> Duration.parse("PT3S")
{:ok, %Duration{seconds: 3}}
"""
@spec parse(String.t) :: {:ok, Duration.t} | {:error, atom}
def parse(value) when is_binary(value) do
case Parser.parse(value) do
{:ok, params, _, _, _, _} ->
{:ok, struct(__MODULE__, params)}
_ ->
{:error, :invalid_duration}
end
end
def parse(_) do
{:error, :parse_error}
end
@doc """
Converts a `Duration.t` into `Timex.shift_options`, wich can be used with `Timex.shift/2`.
## Examples
Go forward
iex > Duration.to_timex_options(%Duration{years: 1})
{:ok, [days: 0, hours: 0, minutes: 0, months: 0, seconds: 0, years: 1]}
Go backward
iex > Duration.to_timex_options(%Duration{years: 1}; :backward)
{:ok, [days: 0, hours: 0, minutes: 0, months: 0, seconds: 0, years: -1]}
"""
def to_timex_options(%Duration{} = duration, direction \\ :forward) when direction in [:forward, :backward] do
options =
case direction do
:forward -> Map.from_struct(duration) |> Map.to_list
:backward -> Map.from_struct(duration) |> Enum.map(fn {k, v} -> {k, v * -1} end)
end
{:ok, options |> Enum.filter(fn {_, v} -> v != 0 end)}
end
end
defimpl String.Chars, for: Duration do
def to_string(value) do
output = "P"
output = output <> if value.years > 0, do: "#{value.years}Y", else: ""
output = output <> if value.months > 0, do: "#{value.months}M", else: ""
output = output <> if value.days > 0, do: "#{value.days}D", else: ""
output = output <> if (value.hours + value.minutes + value.seconds) > 0, do: "T", else: ""
output = output <> if value.hours > 0, do: "#{value.hours}H", else: ""
output = output <> if value.minutes > 0, do: "#{value.minutes}M", else: ""
output = output <> if value.seconds > 0, do: "#{value.seconds}S", else: ""
output = if output in ["P", "PT"], do: "PT0S", else: output
output
end
end
if Code.ensure_loaded?(Phoenix.HTML.Safe) do
defimpl Phoenix.HTML.Safe, for: Duration do
def to_iodata(value), do: to_string(value)
end
end