defmodule Calecto.DateTimeUTC do require Calendar.DateTime import Calecto.Utils @moduledoc """ Calendar DateTime for Ecto for representing only UTC datetimes """ @behaviour Ecto.Type defstruct [:year, :month, :day, :hour, :min, :sec, :usec, :timezone, :abbr, :utc_off, :std_off] @doc """ The Ecto primitive type. """ def type, do: :datetime @doc """ Datetimes are blank when given as strings and the string is blank. """ defdelegate blank?(value), to: Ecto.Type @doc """ Casts to datetime. """ def cast(<>) when sep in [?\s, ?T] do if usec(rest) do from_parts(to_i(year), to_i(month), to_i(day), to_i(hour), to_i(min), to_i(sec), usec(rest)) else :error end end def cast(%Calecto.DateTimeUTC{timezone: "Etc/UTC"} = dt), do: {:ok, dt} def cast(%Calendar.DateTime{timezone: "Etc/UTC"} = dt) do {tag, calecto_dt} = from_parts(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, dt.usec) if tag == :ok do {:ok, calecto_dt} else :error end end def cast(%{"year"=>year, "month"=>month, "day"=>day, "hour"=>hour, "min"=>min, "sec"=>sec, "usec" => usec}) do from_erl({{to_i(year), to_i(month), to_i(day)}, {to_i(hour), to_i(min), to_i(sec), to_i(usec)}}) end def cast(%{"year"=>year, "month"=>month, "day"=>day, "hour"=>hour, "min"=>min, "sec"=>sec}) do cast(%{"year"=>year, "month"=>month, "day"=>day, "hour"=>hour, "min"=>min, "sec"=> sec, "usec" => 0}) end def cast(%{"year"=>year, "month"=>month, "day"=>day, "hour"=>hour, "min"=>min}) do cast(%{"year"=>year, "month"=>month, "day"=>day, "hour"=>hour, "min"=>min, "sec"=> 0}) end def cast(_), do: :error defp from_parts(year, month, day, hour, min, sec, usec) do load({{year, month, day},{hour, min, sec, usec}}) end def from_erl({{year, month, day},{hour, min, sec, usec}}) do from_parts(year, month, day, hour, min, sec, usec) end def from_erl({{year, month, day},{hour, min, sec}}) do from_parts(year, month, day, hour, min, sec, nil) end def from_erl!(tuple) do {:ok, dt} = from_erl(tuple) dt end def to_micro_erl(%Calecto.DateTimeUTC{timezone: "Etc/UTC", year: year, month: month, day: day, hour: hour, min: min, sec: sec, usec: usec}) do {{year, month, day}, {hour, min, sec, usec}} end def to_calendar_date_time(%Calecto.DateTimeUTC{} = calecto_dt_utc) do {:ok, dt} = Calendar.DateTime.from_micro_erl_total_off(to_micro_erl(calecto_dt_utc), "Etc/UTC", 0) dt end @doc """ Converts to erlang style tuples with microseconds added """ def dump(%Calecto.DateTimeUTC{timezone: timezone}) when timezone != "Etc/UTC" do :error end def dump(%Calecto.DateTimeUTC{usec: nil} = dt) do {date_part, _time_part} = to_micro_erl(dt) {:ok, {date_part, {dt.hour, dt.min, dt.sec, 0}}} end def dump(%Calecto.DateTimeUTC{} = dt) do {:ok, to_micro_erl(dt) } end def dump(_), do: :error @doc """ Converts erlang style tuples to `Calendar.DateTime` """ def load({{year, month, day}, {hour, min, sec, usec}}) do { :ok, %Calecto.DateTimeUTC{year: year, month: month, day: day, hour: hour, min: min, sec: sec, usec: usec, abbr: "UTC", timezone: "Etc/UTC", utc_off: 0, std_off: 0} } end def load(_), do: :error end defimpl Calendar.ContainsDateTime, for: Calecto.DateTimeUTC do def dt_struct(data), do: Calecto.DateTimeUTC.to_calendar_date_time(data) end defimpl Calendar.ContainsNaiveDateTime, for: Calecto.DateTimeUTC do def ndt_struct(data) do {date, {h, m, s, u}} = data |> Calecto.DateTimeUTC.to_micro_erl Calendar.NaiveDateTime.from_erl!({date, {h,m,s}}, u) end end defimpl Calendar.ContainsDate, for: Calecto.DateTimeUTC do def date_struct(data) do {date, _} = data |> Calecto.DateTimeUTC.to_micro_erl Calendar.Date.from_erl! date end end defimpl Calendar.ContainsTime, for: Calecto.DateTimeUTC do def time_struct(data) do {_, {h, m, s, u}} = data |> Calecto.DateTimeUTC.to_micro_erl Calendar.Time.from_erl! {h, m, s}, u end end