1defmodule Timex.Convert do 2 @moduledoc false 3 4 @doc """ 5 Converts a map to a Date, NaiveDateTime or DateTime, depending on the amount 6 of date/time information in the map. 7 """ 8 @spec convert_map(map) :: Date.t | DateTime.t | NaiveDateTime.t | {:error, term} 9 def convert_map(%{__struct__: _} = struct) do 10 convert_map(Map.from_struct(struct)) 11 end 12 def convert_map(map) when is_map(map) do 13 case convert_keys(map) do 14 {:error, _} = err -> 15 err 16 datetime_map when is_map(datetime_map) -> 17 year = Map.get(datetime_map, :year) 18 month = Map.get(datetime_map, :month) 19 day = Map.get(datetime_map, :day) 20 cond do 21 not(is_nil(year)) and not(is_nil(month)) and not(is_nil(day)) -> 22 case Map.get(datetime_map, :hour) do 23 nil -> 24 with {:ok, date} <- Date.new(year, month, day), do: date 25 hour -> 26 minute = Map.get(datetime_map, :minute, 0) 27 second = Map.get(datetime_map, :second, 0) 28 us = Map.get(datetime_map, :microsecond, {0, 0}) 29 tz = Map.get(datetime_map, :time_zone, nil) 30 case tz do 31 s when is_binary(s) -> 32 Timex.DateTime.Helpers.construct({{year,month,day},{hour,minute,second,us}}, tz) 33 nil -> 34 {:ok, nd} = NaiveDateTime.new(year, month, day, hour, minute, second, us) 35 nd 36 end 37 end 38 :else -> 39 {:error, :insufficient_date_information} 40 end 41 end 42 end 43 def try_convert(_), do: {:error, :invalid_date} 44 45 @allowed_keys_atom [ 46 :year, :month, :day, 47 :hour, :minute, :min, :mins, :second, :sec, :secs, 48 :milliseconds, :millisecond, :ms, 49 :microsecond 50 ] 51 @allowed_keys Enum.concat(@allowed_keys_atom, Enum.map(@allowed_keys_atom, &Atom.to_string/1)) 52 @valid_keys_map %{ 53 :min => :minute, 54 :mins => :minute, 55 :secs => :second, 56 :sec => :second, 57 :milliseconds => :millisecond, 58 :ms => :millisecond, 59 :microsecond => :microsecond, 60 :tz => :time_zone, 61 :timezone => :time_zone, 62 :time_zone => :time_zone 63 } 64 65 def convert_keys(map) when is_map(map) do 66 Enum.reduce(map, %{}, fn 67 {_, _}, {:error, _} = err -> err 68 {k, v}, acc when k in [:microsecond, "microsecond"] -> 69 case v do 70 {us, pr} when is_integer(us) and pr >= 0 and pr <= 6 -> 71 Map.put(acc, :microsecond, {us, pr}) 72 us when is_integer(us) -> 73 Map.put(acc, :microsecond, {us, 6}) 74 _ -> acc 75 end 76 {k, v}, acc when k in [:milliseconds, "milliseconds", :ms, "ms", :millisecond, "millisecond"] -> 77 case v do 78 n when is_integer(n) -> 79 us = Timex.DateTime.Helpers.construct_microseconds(n*1_000) 80 Map.put(acc, :microsecond, us) 81 :error -> 82 {:error, {:expected_integer, for: k, got: v}} 83 end 84 {k, v}, acc when k in [:tz, "tz", :timezone, "timezone", :time_zone, "time_zone"] -> 85 case v do 86 s when is_binary(s) -> Map.put(acc, :time_zone, s) 87 %{"full_name" => s} -> Map.put(acc, :time_zone, s) 88 _ -> acc 89 end 90 {k, v}, acc when k in @allowed_keys and is_integer(v) -> 91 Map.put(acc, get_valid_key(k), v) 92 {k, v}, acc when k in @allowed_keys and is_binary(v) -> 93 case Integer.parse(v) do 94 {n, _} -> 95 Map.put(acc, get_valid_key(k), n) 96 :error -> 97 {:error, {:expected_integer, for: k, got: v}} 98 end 99 {_, _}, acc -> acc 100 end) 101 end 102 103 defp get_valid_key(key) when is_atom(key), 104 do: Map.get(@valid_keys_map, key, key) 105 106 defp get_valid_key(key), 107 do: key |> String.to_atom() |> get_valid_key() 108end 109