Packages
electric
1.7.2
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.16
1.4.16-beta-1
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
retired
1.1.5
retired
1.1.4
retired
1.1.3
retired
1.1.2
1.1.1
1.1.0
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.19
1.0.18
1.0.17
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-beta.23
1.0.0-beta.22
1.0.0-beta.20
1.0.0-beta.19
1.0.0-beta.18
1.0.0-beta.17
1.0.0-beta.16
1.0.0-beta.15
1.0.0-beta.14
1.0.0-beta.13
1.0.0-beta.12
1.0.0-beta.11
1.0.0-beta.10
1.0.0-beta.9
1.0.0-beta.8
1.0.0-beta.7
1.0.0-beta.6
1.0.0-beta.5
1.0.0-beta.4
1.0.0-beta.3
1.0.0-beta.2
1.0.0-beta.1
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.5.2
0.4.4
Postgres sync engine. Sync little subsets of your Postgres data into local apps and services.
Current section
Files
Jump to
Current section
Files
lib/pg_interop/interval/iso8601_alternative_parser.ex
defmodule PgInterop.Interval.ISO8601AlternativeParser do
@moduledoc """
This module parses alternative ISO-8601 duration strings into Interval structs.
"""
alias PgInterop.Interval
@doc """
Parses an ISO-8601 formatted duration string into a Interval struct.
The parse result is wrapped in a :ok/:error tuple.
## Examples
iex> parse("P0015-3-2T1:14:37.25")
{:ok, Interval.parse!("P15Y3M2DT1H14M37.25S")}
iex> parse("P0015-3-2")
{:ok, Interval.parse!("P15Y3M2D")}
iex> parse("PT3:12:25.001")
{:ok, Interval.parse!("PT3H12M25.001S")}
iex> parse("P0015T30:00")
{:ok, Interval.parse!("P15YT30H")}
iex> parse("")
{:error, "input string cannot be empty"}
iex> parse("W")
{:error, "expected P, got W"}
iex> parse("P0015TT30:00")
{:error, "unexpected duplicate T"}
iex> parse("P0015-3-2-1")
{:error, "unexpected 4th section in y-m-d part"}
iex> parse("P0015-3-y")
{:error, "invalid number `y`"}
iex> parse("P0015-3-1T30:00:10.y")
{:error, "invalid number `10.y`"}
"""
@spec parse(String.t()) :: {:ok, Interval.t()} | {:error, term}
def parse(<<>>), do: {:error, "input string cannot be empty"}
def parse(<<?P, rest::binary>>) do
with {:ok, ymd, hms} <- split_on_time(rest),
{:ok, ymd_duration} <- validate_ymd(ymd),
{:ok, hms_duration} <- validate_hms(hms) do
{:ok, Interval.add(ymd_duration, hms_duration)}
end
end
def parse(<<c::utf8, _::binary>>), do: {:error, "expected P, got #{<<c::utf8>>}"}
def split_on_time(x) do
case String.split(x, "T") do
[x] -> {:ok, x, nil}
[yml, hms] -> {:ok, yml, hms}
_ -> {:error, "unexpected duplicate T"}
end
end
def validate_ymd(ymd) do
case String.split(ymd, "-") do
[_, _, _, _ | _] ->
{:error, "unexpected 4th section in y-m-d part"}
[""] ->
{:ok, Interval.zero()}
ymd ->
with {:ok, ymd} <- validate_all_integers(ymd) do
{:ok,
ymd
|> Enum.zip(~w|years months days|a)
|> Enum.map(&build_duration/1)
|> Enum.reduce(Interval.zero(), &Interval.add/2)}
end
end
end
def validate_hms(nil), do: {:ok, Interval.zero()}
def validate_hms(hms) do
case String.split(hms, ":") do
[_, _, _, _ | _] ->
{:error, "unexpected 4th section in h:m:s part"}
hms ->
with {:ok, hm} <- validate_all_integers(Enum.take(hms, 2)),
{:ok, s} <- validate_float(Enum.at(hms, 2)) do
{:ok,
(hm ++ List.wrap(s))
|> Enum.zip(~w|hours minutes seconds|a)
|> Enum.map(&build_duration/1)
|> Enum.reduce(Interval.zero(), &Interval.add/2)}
end
end
end
defp validate_float(nil), do: {:ok, nil}
defp validate_float(val) do
case Float.parse(val) do
{value, ""} -> {:ok, value}
_ -> {:error, "invalid number `#{val}`"}
end
end
defp validate_all_integers(list), do: Electric.Utils.map_while_ok(list, &cast_int/1)
defp cast_int(int) do
case Integer.parse(int) do
{value, ""} -> {:ok, value}
_ -> {:error, "invalid number `#{int}`"}
end
end
defp build_duration({number, :years}), do: Interval.from_months(12 * number)
defp build_duration({number, :months}), do: Interval.from_months(number)
defp build_duration({number, :days}), do: Interval.from_days(number)
defp build_duration({number, :hours}), do: Interval.from_hours(number)
defp build_duration({number, :minutes}), do: Interval.from_minutes(number)
defp build_duration({number, :seconds}), do: Interval.from_seconds(number)
end