defmodule DateParams do @moduledoc """ An Elixir library that converts specified values within maps into native `Date`, `DateTime`, `NaiveDateTime`, and `Time` structs. """ @doc""" Convert the values within a map to DateTime structs for the given keys. The keys given follows the format for accessing nested structures like in `Kernel.get_in/2`. """ def to_structs(map, keys) do Enum.reduce(keys, map, &transform_values/2) end @doc""" Converts the value to a DateTime struct """ def to_struct(value), do: transform_value(value) # Converts a value to integer for creating dates defp to_integer(value) when is_atom(value), do: value |> Atom.to_string |> String.to_integer defp to_integer(value) when is_bitstring(value), do: String.to_integer(value) defp to_integer(value), do: value # ISO 8601 date and time without seconds (Chrome's datetime input submits this way) defp transform_value(value = <<_::4-bytes, ?-, _::2-bytes, ?-, _::2-bytes, sep, _::2-bytes, ?:, _::2-bytes>>) when sep in [?\s, ?T] do transform_value(value <> ":00") end # ISO 8601 date and time with seconds defp transform_value(value = <<_::4-bytes, ?-, _::2-bytes, ?-, _::2-bytes, sep, _::2-bytes, ?:, _::2-bytes, ?:, _::2-bytes>>) when sep in [?\s, ?T] do case Calendar.NaiveDateTime.Parse.iso8601(value) do {:ok, datetime, nil} -> {value, datetime} {:error, _} -> {value, value} end end # Phoenix-style date-time param maps defp transform_value(value = %{"year" => year, "month" => month, "day" => day, "hour" => hour, "minute" => minute, "second" => second}) do datetime = NaiveDateTime.new(to_integer(year), to_integer(month), to_integer(day), to_integer(hour), to_integer(minute), to_integer(second)) case datetime do {:ok, date} -> {value, date} {:error, _} -> {value, value} end end # ISO 8601 dates defp transform_value(value = <<_::4-bytes, ?-, _::2-bytes, ?-, _::2-bytes>>) do case Calendar.Date.Parse.iso8601(value) do {:ok, date} -> {value, date} {:error, _} -> {value, value} end end # Phoenix-style date param maps defp transform_value(value = %{"year" => year, "month" => month, "day" => day}) do case Date.new(to_integer(year), to_integer(month), to_integer(day)) do {:ok, date} -> {value, date} {:error, _} -> {value, value} end end # Fallback defp transform_value(value), do: {value, value} # Get and covert for the given keys defp transform_values(keys, map) do {_, transformed} = get_and_update_in(map, keys, &transform_value/1) transformed end end