more comprehensive datetime parsing fix

This commit is contained in:
byte[] 2020-01-01 01:00:34 -05:00
parent 975531c42c
commit e3e143e2c6

View file

@ -1,8 +1,6 @@
defmodule Search.DateParser do defmodule Search.DateParser do
import NimbleParsec import NimbleParsec
defp to_int(input), do: Search.Helpers.to_int(input)
defp build_datetime(naive, tz_off, tz_hour, tz_minute) do defp build_datetime(naive, tz_off, tz_hour, tz_minute) do
tz_hour = tz_hour =
tz_hour tz_hour
@ -18,78 +16,78 @@ defmodule Search.DateParser do
# Unbelievable that there is no way to build this with integer arguments. # Unbelievable that there is no way to build this with integer arguments.
# WTF, Elixir? # WTF, Elixir?
{:ok, datetime, _offset} = DateTime.from_iso8601(iso8601_string) case DateTime.from_iso8601(iso8601_string) do
{:ok, datetime, _offset} ->
{:ok, datetime}
datetime _ ->
:error
end
end end
defp timezone_bounds([]), do: ["+", 0, 0] defp timezone_bounds([]), do: ["+", 0, 0]
defp timezone_bounds([tz_off, tz_hour]), do: [tz_off, tz_hour, 0] defp timezone_bounds([tz_off, tz_hour]), do: [tz_off, tz_hour, 0]
defp timezone_bounds([tz_off, tz_hour, tz_minute]), do: [tz_off, tz_hour, tz_minute] defp timezone_bounds([tz_off, tz_hour, tz_minute]), do: [tz_off, tz_hour, tz_minute]
defp days_in_month(year, month) when month in 1..12 do
Calendar.ISO.days_in_month(year, month)
end
defp days_in_month(_year, _month) do
0
end
defp lower_upper(tuple, offset_amount) do
case NaiveDateTime.from_erl(tuple) do
{:ok, lower} ->
upper = NaiveDateTime.add(lower, offset_amount, :second)
{:ok, [lower, upper]}
_ ->
:error
end
end
defp date_bounds([year]) do defp date_bounds([year]) do
lower = %NaiveDateTime{year: year, month: 1, day: 1, hour: 0, minute: 0, second: 0} lower_upper({{year, 1, 1}, {0, 0, 0}}, 31_536_000)
upper = NaiveDateTime.add(lower, 31_536_000, :second)
[lower, upper]
end end
defp date_bounds([year, month]) do defp date_bounds([year, month]) do
lower = %NaiveDateTime{year: year, month: month, day: 1, hour: 0, minute: 0, second: 0} days = days_in_month(year, month)
upper = NaiveDateTime.add(lower, 2_592_000, :second) lower_upper({{year, month, 1}, {0, 0, 0}}, 86_400 * days)
[lower, upper]
end end
defp date_bounds([year, month, day]) do defp date_bounds([year, month, day]) do
lower = %NaiveDateTime{year: year, month: month, day: day, hour: 0, minute: 0, second: 0} lower_upper({{year, month, day}, {0, 0, 0}}, 86_400)
upper = NaiveDateTime.add(lower, 86400, :second)
[lower, upper]
end end
defp date_bounds([year, month, day, hour]) do defp date_bounds([year, month, day, hour]) do
lower = %NaiveDateTime{year: year, month: month, day: day, hour: hour, minute: 0, second: 0} lower_upper({{year, month, day}, {hour, 0, 0}}, 3_600)
upper = NaiveDateTime.add(lower, 3600, :second)
[lower, upper]
end end
defp date_bounds([year, month, day, hour, minute]) do defp date_bounds([year, month, day, hour, minute]) do
lower = %NaiveDateTime{ lower_upper({{year, month, day}, {hour, minute, 0}}, 60)
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: 0
}
upper = NaiveDateTime.add(lower, 60, :second)
[lower, upper]
end end
defp date_bounds([year, month, day, hour, minute, second]) do defp date_bounds([year, month, day, hour, minute, second]) do
lower = %NaiveDateTime{ lower_upper({{year, month, day}, {hour, minute, second}}, 1)
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second
}
upper = NaiveDateTime.add(lower, 1, :second)
[lower, upper]
end end
defp absolute_datetime(opts) do defp absolute_datetime(_rest, opts, context, _line, _offset) do
date = Keyword.fetch!(opts, :date) date = Keyword.fetch!(opts, :date)
timezone = Keyword.get(opts, :timezone, []) timezone = Keyword.get(opts, :timezone, [])
[lower, upper] = date_bounds(date)
[tz_off, tz_hour, tz_minute] = timezone_bounds(timezone) [tz_off, tz_hour, tz_minute] = timezone_bounds(timezone)
lower = build_datetime(lower, tz_off, tz_hour, tz_minute) with {:ok, [lower, upper]} <- date_bounds(date),
upper = build_datetime(upper, tz_off, tz_hour, tz_minute) {:ok, lower} <- build_datetime(lower, tz_off, tz_hour, tz_minute),
{:ok, upper} <- build_datetime(upper, tz_off, tz_hour, tz_minute)
[lower, upper] do
{[[lower, upper]], context}
else
_ ->
date = Enum.join(date ++ timezone, ", ")
{:error, "invalid date format in input, parsed as #{date}"}
end
end end
defp relative_datetime([count, scale]) do defp relative_datetime([count, scale]) do
@ -105,14 +103,9 @@ defmodule Search.DateParser do
choice([string(" "), string("\t"), string("\n"), string("\r"), string("\v"), string("\f")]) choice([string(" "), string("\t"), string("\n"), string("\r"), string("\v"), string("\f")])
|> ignore() |> ignore()
pos_2dig_int =
ascii_char('0123456789')
|> ascii_char('123456789')
|> reduce(:to_int)
year = integer(4) year = integer(4)
month = pos_2dig_int month = integer(2)
day = pos_2dig_int day = integer(2)
hour = integer(2) hour = integer(2)
minute = integer(2) minute = integer(2)
@ -164,7 +157,7 @@ defmodule Search.DateParser do
absolute_date = absolute_date =
date_part date_part
|> optional(timezone_part) |> optional(timezone_part)
|> reduce(:absolute_datetime) |> post_traverse(:absolute_datetime)
|> unwrap_and_tag(:date) |> unwrap_and_tag(:date)
relative_date = relative_date =