Current section

Files

Jump to
relative_datetime lib relative_datetime.ex
Raw

lib/relative_datetime.ex

defmodule RelativeDateTime do
@moduledoc """
Parse a relative datetime string and return a datetime.
"""
import NimbleParsec
whitespace = string(" ")
dash = string("-")
now = string("now") |> unwrap_and_tag(:now)
ago = string("ago") |> replace(-1) |> unwrap_and_tag(:multiplier)
number = integer(min: 1) |> unwrap_and_tag(:amount)
# use replace here - get rid of to_unit
unit =
choice([
string("second") |> replace(:seconds),
string("minute") |> replace(:minutes),
string("hour") |> replace(:hours),
string("day") |> replace(:days),
string("week") |> replace(:weeks),
string("month") |> replace(:months),
string("year") |> replace(:years)
])
|> ignore(string("s"))
|> unwrap_and_tag(:unit)
numeric_month2 =
~w(01 02 03 04 05 06 07 08 09 10 11 12)
|> Enum.map(&string/1)
|> choice()
numeric_month1 =
1..9
|> Enum.map(&to_string/1)
|> Enum.map(&string/1)
|> choice()
|> lookahead_not(integer(1))
day2 =
(~w(01 02 03 04 05 06 07 08 09) ++ Enum.map(10..31, &to_string/1))
|> Enum.map(&string/1)
|> choice()
day1 =
1..9
|> Enum.map(&to_string/1)
|> Enum.map(&string/1)
|> choice()
|> lookahead_not(integer(1))
# return integer
year =
[?0..?9]
|> ascii_char()
|> times(4)
|> reduce({:++, [[]]})
|> map({Kernel, :to_string, []})
|> map({String, :to_integer, []})
|> unwrap_and_tag(:year)
# return intger
month =
choice([numeric_month2, numeric_month1])
|> map({String, :to_integer, []})
|> unwrap_and_tag(:month)
day =
choice([
day2,
day1
])
|> map({String, :to_integer, []})
|> unwrap_and_tag(:day)
root =
choice([
now,
year |> ignore(dash) |> concat(month) |> ignore(dash) |> concat(day),
number |> ignore(whitespace) |> concat(unit) |> optional(ignore(whitespace) |> concat(ago))
])
defparsec(:parse_datetime, root)
@doc """
Parses a given relative datetime string and returns a datetime.
"""
def parse(relative, datetime) do
case parse_datetime(relative) do
{:ok, [now: "now"], _, _, _, _} ->
{:ok, datetime}
{:ok, [year: year, month: month, day: day], "", _, _, _} ->
{:ok, Date.new!(year, month, day) |> DateTime.new!(~T[00:00:00])}
{:ok, args, "", _, _, _} ->
{:ok, apply_relative_datetime(datetime, Map.new(args))}
_ ->
:error
end
end
#
defp apply_relative_datetime(datetime, %{amount: amount, unit: unit} = args) do
datetime
|> Timex.shift([{unit, amount * Map.get(args, :multiplier, 1)}])
end
end