Packages
expression
2.49.0
2.49.1
2.49.0
2.48.0
2.47.5
2.47.4
2.47.3
2.47.2
2.47.1
2.47.0
2.46.0
2.45.1
2.45.0
2.44.1
2.44.0
2.43.0
2.42.1
2.41.4
2.41.3
2.41.2
2.41.0
2.40.0
2.38.0
2.37.0
2.36.2
2.36.1
2.36.0
2.35.0
2.34.0
2.33.1
2.33.0
2.32.1
2.31.6
2.31.3
2.31.0
2.30.0
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.1
2.26.0
2.25.0
2.24.2
2.24.1
2.24.0
2.23.9
2.23.8
2.23.7
2.23.6
2.23.4
2.23.3
2.23.2
2.23.1
2.23.0
2.22.2
2.22.0
2.21.0
2.20.0
2.19.0
2.18.0
2.17.0
2.16.0
2.14.0
2.13.0
2.12.0
2.11.0
2.10.1
2.10.0
2.9.0
2.8.0
2.6.0
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.1
2.4.0
2.3.2
2.3.1
2.3.0
2.2.1
2.2.0
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.1
1.1.0
1.0.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.1.0
A Excel like expression parser, compatible with FLOIP Expression language.
Current section
Files
Jump to
Current section
Files
lib/date_helpers.ex
defmodule Expression.DateHelpers do
@moduledoc false
import NimbleParsec
def date_separator(combinator \\ empty()) do
combinator
|> choice([
string("-"),
string("/")
])
end
def us_date(combinator \\ empty()) do
combinator
|> integer(2)
|> ignore(date_separator())
|> integer(2)
|> ignore(date_separator())
|> integer(4)
end
def plain_time(combinator \\ empty()) do
combinator
|> integer(2)
|> ignore(string(":"))
|> integer(2)
|> optional(ignore(string(":")))
|> optional(integer(2))
end
def us_datetime(combinator \\ empty()) do
combinator
|> us_date()
|> ignore(string(" "))
|> concat(plain_time())
end
def iso_date(combinator \\ empty()) do
combinator
|> integer(4)
|> ignore(date_separator())
|> integer(2)
|> ignore(date_separator())
|> integer(2)
end
def iso_time(combinator \\ empty()) do
combinator
|> integer(2)
|> ignore(string(":"))
|> integer(2)
|> ignore(string(":"))
|> integer(2)
|> ignore(optional(string(".")))
|> optional(integer(min: 1))
|> choice([
ignore(string("+")) |> integer(min: 1),
string("Z") |> replace(0),
empty()
])
end
def iso_datetime(combinator \\ empty()) do
combinator
|> iso_date()
|> ignore(string("T"))
|> concat(iso_time())
end
def time(combinator \\ empty()) do
combinator
|> plain_time()
|> reduce(:to_time)
end
def date(combinator \\ empty()) do
combinator
|> choice([
tag(us_date(), :us_format),
tag(iso_date(), :iso_format)
])
|> reduce(:to_date)
end
def datetime(combinator \\ empty()) do
combinator
|> choice([
tag(us_datetime(), :us_format),
tag(iso_datetime(), :iso_format)
])
|> reduce(:to_datetime)
end
def to_time(parsed_values) do
values =
[:hour, :minute, :second]
|> Enum.zip(parsed_values)
Time.new!(values[:hour], values[:minute], values[:second] || 0)
end
def to_datetime(opts) do
values =
case opts do
[iso_format: parsed_value] ->
values =
[:year, :month, :day, :hour, :minute, :second, :microsecond, :utc_offset]
|> Enum.zip(parsed_value)
{microseconds, values} = Keyword.pop(values, :microsecond, 0)
# Elixir's DateTime only supports microseconds up to precision 6
microseconds_string = to_string(microseconds)
microseconds_precision = min(String.length(microseconds_string), 6)
microseconds =
microseconds_string
|> String.slice(0, microseconds_precision)
|> String.to_integer()
Keyword.put(values, :microsecond, {microseconds, microseconds_precision})
[us_format: parsed_value] ->
[:day, :month, :year, :hour, :minute, :second]
|> Enum.zip(parsed_value)
end
fields =
[
calendar: Calendar.ISO,
hour: 0,
minute: 0,
second: 0,
time_zone: "Etc/UTC",
zone_abbr: "UTC",
utc_offset: 0,
std_offset: 0
]
|> Keyword.merge(values)
struct(DateTime, fields)
end
def to_date(opts) do
values =
case opts do
[iso_format: parsed_value] ->
Enum.zip([:year, :month, :day], parsed_value)
[us_format: parsed_value] ->
Enum.zip([:day, :month, :year], parsed_value)
end
Date.new!(values[:year], values[:month], values[:day])
end
@spec extract_dateish(DateTime.t() | Date.t() | String.t() | nil) :: Date.t() | nil
def extract_dateish(date_time) when is_struct(date_time, DateTime), do: date_time
def extract_dateish(date) when is_struct(date, Date), do: date
def extract_dateish(nil), do: nil
def extract_dateish({:not_found, _}), do: nil
def extract_dateish(expression) when is_binary(expression) do
expression = Regex.replace(~r/[a-z]/u, expression, "")
case Expression.parse_expression(expression) do
{:ok, [{:literal, datetime}]} when is_struct(datetime, DateTime) ->
DateTime.to_date(datetime)
{:ok, [{:literal, date}]} when is_struct(date, Date) ->
date
_other ->
nil
end
end
@spec extract_datetimeish(DateTime.t() | Date.t() | String.t() | term) :: DateTime.t() | nil
def extract_datetimeish(date_time) when is_struct(date_time, DateTime), do: date_time
def extract_datetimeish(date) when is_struct(date, Date),
do: DateTime.new!(date, Time.new!(0, 0, 0, 0))
def extract_datetimeish(expression) when is_binary(expression) do
expression = Regex.replace(~r/[a-z]/u, expression, "")
case Expression.parse_expression(expression) do
{:ok, [{:literal, datetime}]} when is_struct(datetime, DateTime) ->
datetime
{:ok, [{:literal, date}]} when is_struct(date, Date) ->
DateTime.new!(date, ~T[00:00:00])
_other ->
nil
end
end
def extract_datetimeish(_term), do: nil
@spec extract_timeish(DateTime.t() | Time.t() | String.t()) :: Time.t() | nil
def extract_timeish(datetime) when is_struct(datetime, DateTime),
do: DateTime.to_time(datetime)
def extract_timeish(time) when is_struct(time, Time),
do: time
def extract_timeish(expression) when is_binary(expression) do
expression = Regex.replace(~r/[a-z\s]/u, expression, "")
case Expression.parse_expression(expression) do
{:ok, [{:literal, datetime}]} when is_struct(datetime, DateTime) ->
DateTime.to_time(datetime)
{:ok, [{:literal, time}]} when is_struct(time, Time) ->
time
{:ok, result} ->
result
_other ->
nil
end
end
end