Packages
timex
3.7.12
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/datetime/parsers/iso8601_extended.ex
defmodule Timex.Parse.DateTime.Parsers.ISO8601Extended do
use Combine.Helpers
alias Combine.ParserState
defparser parse(%ParserState{status: :ok, column: col, input: input, results: results} = state) do
case parse_extended(input) do
{:ok, parts, len, remaining} ->
%{
state
| :column => col + len,
:input => remaining,
:results => [Enum.reverse(parts) | results]
}
{:error, reason, count} ->
%{state | :status => :error, :column => col + count, :error => reason}
end
end
def parse_extended(<<>>), do: {:error, "Expected year, but got end of input."}
def parse_extended(input), do: parse_extended(input, :year, [], 0)
def parse_extended(
<<y1::utf8, y2::utf8, y3::utf8, y4::utf8, "-", rest::binary>>,
:year,
acc,
count
)
when y1 >= ?0 and y1 <= ?9 and
y2 >= ?0 and y2 <= ?9 and
y3 >= ?0 and y3 <= ?9 and
y4 >= ?0 and y4 <= ?9 do
year = String.to_integer(<<y1::utf8, y2::utf8, y3::utf8, y4::utf8>>)
parse_extended(rest, :month, [{:year4, year} | acc], count + 4)
end
def parse_extended(<<h::utf8, _::binary>>, :year, _acc, count),
do: {:error, "Expected 4 digit year, but got `#{<<h::utf8>>}` instead.", count}
def parse_extended(_, :year, _acc, count),
do: {:error, "Expected 4 digit year.", count}
def parse_extended(<<m1::utf8, m2::utf8, "-", rest::binary>>, :month, acc, count)
when m1 >= ?0 and m1 < ?2 and
m2 >= ?0 and m2 <= ?9 do
month = String.to_integer(<<m1::utf8, m2::utf8>>)
cond do
month > 0 and month < 13 ->
parse_extended(rest, :day, [{:month, month} | acc], count + 2)
:else ->
{:error, "Expected month between 1-12, but got `#{month}` instead.", count}
end
end
def parse_extended(<<h::utf8, _::binary>>, :month, _acc, count),
do: {:error, "Expected 2 digit month, but got `#{<<h::utf8>>}` instead.", count}
def parse_extended(_, :month, _acc, count),
do: {:error, "Expected 2 digit month.", count}
def parse_extended(<<d1::utf8, d2::utf8, sep::utf8, rest::binary>>, :day, acc, count)
when d1 >= ?0 and d1 <= ?3 and
d2 >= ?0 and d2 <= ?9 do
cond do
sep in [?T, ?\s] ->
day = String.to_integer(<<d1::utf8, d2::utf8>>)
cond do
day > 0 and day < 32 ->
parse_extended(rest, :hour, [{:day, day} | acc], count + 3)
:else ->
{:error, "Expected day between 1-31, but got `#{day}` instead.", count}
end
:else ->
{:error,
"Expected valid date/time separator (T or space), but got `#{<<sep::utf8>>}` instead.",
count + 2}
end
end
def parse_extended(<<h::utf8, _::binary>>, :day, _acc, count),
do: {:error, "Expected 2 digit day, but got `#{<<h::utf8>>}` instead.", count}
def parse_extended(_, :day, _acc, count),
do: {:error, "Expected 2 digit day.", count}
def parse_extended(<<h1::utf8, h2::utf8, rest::binary>>, :hour, acc, count)
when h1 >= ?0 and h1 < ?3 and
h2 >= ?0 and h2 <= ?9 do
hour = String.to_integer(<<h1::utf8, h2::utf8>>)
cond do
hour >= 0 and hour <= 24 ->
case rest do
<<":", rest::binary>> ->
parse_extended(rest, :minute, [{:hour24, hour} | acc], count + 3)
_ ->
parse_offset(rest, [{:hour24, hour} | acc], count + 2)
end
:else ->
{:error, "Expected hour between 0-24, but got `#{hour}` instead.", count}
end
end
def parse_extended(<<h::utf8, _::binary>>, :hour, _acc, count),
do: {:error, "Expected 2 digit hour, but got `#{<<h::utf8>>}` instead.", count}
def parse_extended(_, :hour, _acc, count),
do: {:error, "Expected 2 digit hour.", count}
# Minutes are optional
def parse_extended(<<m1::utf8, m2::utf8, rest::binary>>, :minute, acc, count)
when m1 >= ?0 and m1 < ?6 and
m2 >= ?0 and m2 <= ?9 do
minute = String.to_integer(<<m1::utf8, m2::utf8>>)
cond do
minute >= 0 and minute <= 60 ->
case rest do
<<":", rest::binary>> ->
parse_extended(rest, :second, [{:min, minute} | acc], count + 3)
_ ->
parse_offset(rest, [{:min, minute} | acc], count + 2)
end
:else ->
{:error, "Expected minute between 0-60, but got `#{minute}` instead.", count}
end
end
def parse_extended(<<m1::utf8, _::binary>>, :minute, _acc, count),
do: {:error, "Expected 2 digit minute, but got `#{<<m1::utf8>>}` instead.", count}
def parse_extended(_, :minute, _acc, count),
do: {:error, "Expected 2 digit minute.", count}
# Seconds are optional
# Has fractional seconds
def parse_extended(<<s1::utf8, s2::utf8, ".", rest::binary>>, :second, acc, count)
when s1 >= ?0 and s1 < ?6 and
s2 >= ?0 and s2 <= ?9 do
case parse_fractional_seconds(rest, count, <<>>) do
{:ok, fraction, count, rest} ->
seconds = String.to_integer(<<s1::utf8, s2::utf8>>)
precision = byte_size(fraction)
fraction = if precision > 6, do: binary_part(fraction, 0, 6), else: fraction
precision = if precision > 6, do: 6, else: precision
fractional = String.to_integer(fraction)
fractional = fractional * div(1_000_000, trunc(:math.pow(10, precision)))
cond do
seconds >= 0 and seconds <= 60 ->
parse_offset(
rest,
[{:sec_fractional, {fractional, precision}}, {:sec, seconds} | acc],
count + 2
)
:else ->
{:error, "Expected second between 0-60, but got `#{seconds}` instead.", count}
end
{:error, _reason, _count} = err ->
err
end
end
# No fractional seconds
def parse_extended(<<s1::utf8, s2::utf8, rest::binary>>, :second, acc, count)
when s1 >= ?0 and s1 < ?6 and
s2 >= ?0 and s2 <= ?9 do
second = String.to_integer(<<s1::utf8, s2::utf8>>)
cond do
second >= 0 and second <= 60 ->
parse_offset(rest, [{:sec, second} | acc], count + 2)
:else ->
{:error, "Expected second between 0-60, but got `#{second}` instead.", count}
end
end
def parse_extended(<<h::utf8, _::binary>>, :second, _acc, count),
do: {:error, "Expected valid value for seconds, but got `#{<<h::utf8>>}` instead.", count}
def parse_extended(_, :second, _acc, count),
do: {:error, "Expected valid value for seconds.", count}
def parse_fractional_seconds(<<digit::utf8, rest::binary>>, count, acc)
when digit >= ?0 and digit <= ?9 do
parse_fractional_seconds(rest, count + 1, <<acc::binary, digit::utf8>>)
end
def parse_fractional_seconds(_rest, count, "") do
{:error, "Expected at least one digit after the decimal sign, but found none", count}
end
def parse_fractional_seconds(rest, count, acc) do
{:ok, acc, count, rest}
end
def parse_offset(<<"Z", rest::binary>>, acc, count),
do: {:ok, [{:zname, "Etc/UTC"} | acc], count + 1, rest}
def parse_offset(<<dir::utf8, rest::binary>>, acc, count) when dir in [?+, ?-] do
parse_offset(dir, rest, acc, count + 1)
end
def parse_offset("", acc, count), do: {:ok, acc, count, ""}
def parse_offset(str, _acc, count),
do: {:error, "Expected either Z or a valid timezone offset, but got `#{str}`", count}
# +/-HH:MM:SS (seconds are currently unhandled in offsets)
def parse_offset(
dir,
<<h1::utf8, h2::utf8, ":", m1::utf8, m2::utf8, ":", s1::utf8, s2::utf8, rest::binary>>,
acc,
count
)
when h1 >= ?0 and h1 < ?2 and
h2 >= ?0 and h2 <= ?9 and
m1 >= ?0 and m1 < ?6 and
m2 >= ?0 and m2 <= ?9 and
s1 >= ?0 and s1 < ?6 and
s2 >= ?0 and s2 <= ?9 do
{:ok, [{:zname, <<dir::utf8, h1::utf8, h2::utf8, ":", m1::utf8, m2::utf8>>} | acc], count + 7,
rest}
end
# +/-HH:MM
def parse_offset(dir, <<h1::utf8, h2::utf8, ":", m1::utf8, m2::utf8, rest::binary>>, acc, count)
when h1 >= ?0 and h1 < ?2 and
h2 >= ?0 and h2 <= ?9 and
m1 >= ?0 and m1 < ?6 and
m2 >= ?0 and m2 <= ?9 do
{:ok, [{:zname, <<dir::utf8, h1::utf8, h2::utf8, ":", m1::utf8, m2::utf8>>} | acc], count + 5,
rest}
end
# +/-HHMM
def parse_offset(dir, <<h1::utf8, h2::utf8, m1::utf8, m2::utf8, rest::binary>>, acc, count)
when h1 >= ?0 and h1 < ?2 and
h2 >= ?0 and h2 <= ?9 and
m1 >= ?0 and m1 < ?6 and
m2 >= ?0 and m2 <= ?9 do
{:ok, [{:zname, <<dir::utf8, h1::utf8, h2::utf8, ":", m1::utf8, m2::utf8>>} | acc], count + 5,
rest}
end
# +/-HH
def parse_offset(dir, <<h1::utf8, h2::utf8, rest::binary>>, acc, count)
when h1 >= ?0 and h1 < ?2 and
h2 >= ?0 and h2 <= ?9 do
{:ok, [{:zname, <<dir::utf8, h1::utf8, h2::utf8, ":00">>} | acc], count + 2, rest}
end
def parse_offset(_, <<h::utf8, _rest::binary>>, _acc, count),
do: {:error, "Expected valid offset, but got `#{<<h::utf8>>}` instead.", count}
end