defmodule RelativeDate.Parser do import NimbleParsec time_specifier = choice([ string("second") |> replace(1), string("minute") |> replace(60), string("hour") |> replace(3_600), string("day") |> replace(86_400), string("week") |> replace(604_800), string("month") |> replace(2_592_000), string("year") |> replace(31_536_000) ]) |> ignore(optional(string("s"))) direction_specifier = choice([ string("ago") |> replace(-1), string("from now") |> replace(1) ]) space = ignore(repeat(string(" "))) moon = space |> string("moon") |> concat(space) |> eos() |> unwrap_and_tag(:moon) date = space |> integer(min: 1) |> concat(space) |> concat(time_specifier) |> concat(space) |> concat(direction_specifier) |> concat(space) |> eos() |> tag(:relative_date) relative_date = choice([ moon, date ]) defparsecp :relative_date, relative_date def parse(input) do input = input |> to_string() |> String.trim() case parse_absolute(input) do {:ok, datetime} -> {:ok, datetime} _error -> parse_relative(input) end end def parse_absolute(input) do case DateTime.from_iso8601(input) do {:ok, datetime, _offset} -> {:ok, datetime |> DateTime.truncate(:second)} _error -> {:error, "Parse error"} end end def parse_relative(input) do case relative_date(input) do {:ok, [moon: _moon], _1, _2, _3, _4} -> {:ok, DateTime.utc_now() |> DateTime.add(31_536_000_000, :second) |> DateTime.truncate(:second)} {:ok, [relative_date: [amount, scale, direction]], _1, _2, _3, _4} -> {:ok, DateTime.utc_now() |> DateTime.add(amount * scale * direction, :second) |> DateTime.truncate(:second)} _error -> {:error, "Parse error"} end end end