Packages
timex
3.7.13
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/parse/duration/parsers/iso8601.ex
defmodule Timex.Parse.Duration.Parsers.ISO8601Parser do
@moduledoc """
This module parses ISO-8601 duration strings into Duration structs.
"""
use Timex.Parse.Duration.Parser
@numeric ~c".0123456789"
@doc """
Parses an ISO-8601 formatted duration string into a Duration struct.
The parse result is wrapped in a :ok/:error tuple.
## Examples
iex> {:ok, d} = #{__MODULE__}.parse("P15Y3M2DT1H14M37.25S")
...> Timex.Format.Duration.Formatter.format(d)
"P15Y3M2DT1H14M37.25S"
iex> {:ok, d} = #{__MODULE__}.parse("P15Y3M2D")
...> Timex.Format.Duration.Formatter.format(d)
"P15Y3M2D"
iex> {:ok, d} = #{__MODULE__}.parse("PT3H12M25.001S")
...> Timex.Format.Duration.Formatter.format(d)
"PT3H12M25.001S"
iex> {:ok, d} = #{__MODULE__}.parse("P2W")
...> Timex.Format.Duration.Formatter.format(d)
"P14D"
iex> #{__MODULE__}.parse("P15YT3D")
{:error, "invalid use of date component after time separator"}
"""
@spec parse(String.t()) :: {:ok, Duration.t()} | {:error, term}
def parse(<<>>), do: {:error, "input string cannot be empty"}
def parse(<<?P, rest::binary>>) do
case parse_components(rest, []) do
{:error, _} = err ->
err
[{?W, w}] ->
{:ok, Duration.from_days(7 * w)}
components when is_list(components) ->
result =
Enum.reduce(components, {false, Duration.zero()}, fn
_, {:error, _} = err ->
err
{?Y, y}, {false, d} ->
{false, Duration.add(d, Duration.from_days(365 * y))}
{?M, m}, {false, d} ->
{false, Duration.add(d, Duration.from_days(30 * m))}
{?D, dd}, {false, d} ->
{false, Duration.add(d, Duration.from_days(dd))}
?T, {false, d} ->
{true, d}
?T, {true, _d} ->
{:error, "encountered duplicate time separator T"}
{?H, h}, {true, d} ->
{true, Duration.add(d, Duration.from_hours(h))}
{?M, m}, {true, d} ->
{true, Duration.add(d, Duration.from_minutes(m))}
{?S, s}, {true, d} ->
{true, Duration.add(d, Duration.from_seconds(s))}
{?W, _w}, {_, _} ->
{:error,
"Found 'W', a basic format designator, but the parse indicates " <>
"this is an extended format, mixing the two formats is disallowed"}
{unit, _}, {true, _d} when unit in [?Y, ?D] ->
{:error, "invalid use of date component after time separator"}
{unit, _}, {false, _d} when unit in [?H, ?S] ->
{:error, "missing T separator between date and time components"}
end)
case result do
{:error, _} = err -> err
{_, duration} -> {:ok, duration}
end
end
end
def parse(<<c::utf8, _::binary>>), do: {:error, "expected P, got #{<<c::utf8>>}"}
def parse(s) when is_binary(s), do: {:error, "unexpected end of input"}
@spec parse_components(binary, [{integer, number}]) ::
[{integer, number}] | {:error, String.t()}
defp parse_components(<<>>, acc),
do: Enum.reverse(acc)
defp parse_components(<<?T>>, _acc),
do: {:error, "unexpected end of input at T"}
defp parse_components(<<?T, rest::binary>>, acc),
do: parse_components(rest, [?T | acc])
defp parse_components(<<c::utf8>>, _acc) when c in @numeric,
do: {:error, "unexpected end of input at #{<<c::utf8>>}"}
defp parse_components(<<c::utf8, rest::binary>>, acc) when c in @numeric do
case parse_component(rest, {:integer, <<c::utf8>>}) do
{:error, _} = err -> err
{u, n, rest} -> parse_components(rest, [{u, n} | acc])
end
end
defp parse_components(<<c::utf8>>, _acc),
do: {:error, "unexpected end of input at #{<<c::utf8>>}"}
defp parse_components(<<c::utf8, _::binary>>, _acc),
do: {:error, "expected numeric, but got #{<<c::utf8>>}"}
@spec parse_component(binary, {:float | :integer, binary}) ::
{integer, number, binary} | {:error, msg :: binary()}
defp parse_component(<<c::utf8>>, _acc) when c in @numeric,
do: {:error, "unexpected end of input at #{<<c::utf8>>}"}
defp parse_component(<<c::utf8>>, {type, acc}) when c in ~c"WYMDHS" do
case cast_number(type, acc) do
{n, _} -> {c, n, <<>>}
:error -> {:error, "invalid number `#{acc}`"}
end
end
defp parse_component(<<".", rest::binary>>, {:integer, acc}) do
parse_component(rest, {:float, <<acc::binary, ".">>})
end
defp parse_component(<<c::utf8, rest::binary>>, {:integer, acc}) when c in @numeric do
parse_component(rest, {:integer, <<acc::binary, c::utf8>>})
end
defp parse_component(<<c::utf8, rest::binary>>, {:float, acc}) when c in @numeric do
parse_component(rest, {:float, <<acc::binary, c::utf8>>})
end
defp parse_component(<<c::utf8, rest::binary>>, {type, acc}) when c in ~c"WYMDHS" do
case cast_number(type, acc) do
{n, _} -> {c, n, rest}
:error -> {:error, "invalid number `#{acc}`"}
end
end
defp parse_component(<<c::utf8>>, _acc), do: {:error, "unexpected token #{<<c::utf8>>}"}
defp parse_component(<<c::utf8, _::binary>>, _acc),
do: {:error, "unexpected token #{<<c::utf8>>}"}
@spec cast_number(:float | :integer, binary) :: {number(), binary()} | :error
defp cast_number(:integer, binary), do: Integer.parse(binary)
defp cast_number(:float, binary), do: Float.parse(binary)
end