Current section

Files

Jump to
timex lib datetime erlang.ex
Raw

lib/datetime/erlang.ex

defimpl Timex.Protocol, for: Tuple do
alias Timex.AmbiguousDateTime
import Timex.Macros
@epoch :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}})
def to_julian(date) do
with {y, m, d} <- to_erl_datetime(date),
do: Timex.Calendar.Julian.julian_date(y, m, d)
end
def to_gregorian_seconds(date) do
with {:ok, date} <- to_erl_datetime(date),
do: :calendar.datetime_to_gregorian_seconds(date)
end
def to_gregorian_microseconds(date) do
with {:ok, erl_date} <- to_erl_datetime(date),
do:
:calendar.datetime_to_gregorian_seconds(erl_date) * 1_000 * 1_000 +
get_microseconds(date)
end
def to_unix(date) do
with {:ok, date} <- to_erl_datetime(date),
do: :calendar.datetime_to_gregorian_seconds(date) - @epoch
end
def to_date(date) do
with {:ok, {date, _}} <- to_erl_datetime(date),
do: Date.from_erl!(date)
end
def to_datetime({_y, _m, _d} = date, timezone) do
to_datetime({date, {0, 0, 0}}, timezone)
end
def to_datetime({{_y, _m, _d} = date, {h, mm, s}}, timezone) do
to_datetime({date, {h, mm, s, {0, 0}}}, timezone)
end
def to_datetime({{y, m, d}, {h, mm, s, us}}, timezone) when is_datetime(y, m, d, h, mm, s) do
us = Timex.DateTime.Helpers.construct_microseconds(us)
dt = Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us)
with %DateTime{} = datetime <- Timex.Timezone.convert(dt, timezone) do
datetime
else
%AmbiguousDateTime{} = datetime ->
datetime
{:error, _} = err ->
err
end
end
def to_datetime(_, _), do: {:error, :invalid_date}
def to_naive_datetime({{y, m, d}, {h, mm, s, us}}) when is_datetime(y, m, d, h, mm, s) do
us = Timex.DateTime.Helpers.construct_microseconds(us)
Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us)
end
def to_naive_datetime(date) do
with {:ok, {{y, m, d}, {h, mm, s}}} <- to_erl_datetime(date) do
Timex.NaiveDateTime.new!(y, m, d, h, mm, s)
end
end
def to_erl({y, m, d} = date) when is_date(y, m, d), do: date
def to_erl(date) do
with {:ok, date} <- to_erl_datetime(date),
do: date
end
def century({y, m, d}) when is_date(y, m, d), do: Timex.century(y)
def century({{y, m, d}, _}) when is_date(y, m, d), do: Timex.century(y)
def century(_), do: {:error, :invalid_date}
def is_leap?({y, m, d}) when is_date(y, m, d), do: :calendar.is_leap_year(y)
def is_leap?({{y, m, d}, _}) when is_date(y, m, d), do: :calendar.is_leap_year(y)
def is_leap?(_), do: {:error, :invalid_date}
def beginning_of_day({y, m, d} = date) when is_date(y, m, d), do: date
def beginning_of_day({{y, m, d} = date, _}) when is_date(y, m, d),
do: {date, {0, 0, 0}}
def beginning_of_day(_), do: {:error, :invalid_date}
def end_of_day({y, m, d} = date) when is_date(y, m, d), do: date
def end_of_day({{y, m, d} = date, _}) when is_date(y, m, d),
do: {date, {23, 59, 59}}
def end_of_day(_), do: {:error, :invalid_date}
def beginning_of_week({y, m, d}, weekstart) when is_date(y, m, d) do
with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do
Timex.Date.new!(y, m, d)
|> Timex.Date.beginning_of_week(weekstart)
|> Date.to_erl()
end
end
def beginning_of_week({{y, m, d}, _}, weekstart) when is_date(y, m, d) do
with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do
date =
Timex.Date.new!(y, m, d)
|> Timex.Date.beginning_of_week(ws)
|> Date.to_erl()
{date, {0, 0, 0}}
end
end
def beginning_of_week(_, _), do: {:error, :invalid_date}
def end_of_week({y, m, d}, weekstart) when is_date(y, m, d) do
with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do
Timex.Date.new!(y, m, d)
|> Timex.Date.end_of_week(ws)
|> Date.to_erl()
end
end
def end_of_week({{y, m, d}, _}, weekstart) when is_date(y, m, d) do
with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do
date =
Timex.Date.new!(y, m, d)
|> Timex.Date.end_of_week(ws)
|> Date.to_erl()
{date, {23, 59, 59}}
end
end
def end_of_week(_, _), do: {:error, :invalid_date}
def beginning_of_year({y, m, d}) when is_date(y, m, d),
do: {y, 1, 1}
def beginning_of_year({{y, m, d}, _}) when is_date(y, m, d),
do: {{y, 1, 1}, {0, 0, 0}}
def beginning_of_year(_), do: {:error, :invalid_date}
def end_of_year({y, m, d}) when is_date(y, m, d),
do: {y, 12, 31}
def end_of_year({{y, m, d}, _}) when is_date(y, m, d),
do: {{y, 12, 31}, {23, 59, 59}}
def end_of_year(_), do: {:error, :invalid_date}
def beginning_of_quarter({y, m, d}) when is_date(y, m, d) do
month = 1 + 3 * (Timex.quarter(m) - 1)
{y, month, 1}
end
def beginning_of_quarter({{y, m, d}, {h, mm, s} = _time}) when is_datetime(y, m, d, h, mm, s) do
month = 1 + 3 * (Timex.quarter(m) - 1)
{{y, month, 1}, {0, 0, 0}}
end
def beginning_of_quarter({{y, m, d}, {h, mm, s, _us} = _time})
when is_datetime(y, m, d, h, mm, s) do
month = 1 + 3 * (Timex.quarter(m) - 1)
{{y, month, 1}, {0, 0, 0, 0}}
end
def beginning_of_quarter(_), do: {:error, :invalid_date}
def end_of_quarter({y, m, d}) when is_date(y, m, d) do
month = 3 * Timex.quarter(m)
end_of_month({y, month, d})
end
def end_of_quarter({{y, m, d}, {h, mm, s} = time}) when is_datetime(y, m, d, h, mm, s) do
month = 3 * Timex.quarter(m)
end_of_month({{y, month, d}, time})
end
def end_of_quarter({{y, m, d}, {h, mm, s, _us}}) when is_datetime(y, m, d, h, mm, s) do
month = 3 * Timex.quarter(m)
end_of_month({{y, month, d}, {h, mm, s}})
end
def end_of_quarter(_), do: {:error, :invalid_date}
def beginning_of_month({y, m, d}) when is_date(y, m, d),
do: {y, m, 1}
def beginning_of_month({{y, m, d}, _}) when is_date(y, m, d),
do: {{y, m, 1}, {0, 0, 0}}
def beginning_of_month(_), do: {:error, :invalid_date}
def end_of_month({y, m, d} = date) when is_date(y, m, d),
do: {y, m, days_in_month(date)}
def end_of_month({{y, m, d}, _} = date) when is_date(y, m, d),
do: {{y, m, days_in_month(date)}, {23, 59, 59}}
def end_of_month(_), do: {:error, :invalid_date}
def quarter({y, m, d}) when is_date(y, m, d), do: Calendar.ISO.quarter_of_year(y, m, d)
def quarter({{y, m, d}, _}) when is_date(y, m, d), do: Calendar.ISO.quarter_of_year(y, m, d)
def quarter(_), do: {:error, :invalid_date}
def days_in_month({y, m, d}) when is_date(y, m, d), do: Timex.days_in_month(y, m)
def days_in_month({{y, m, d}, _}) when is_date(y, m, d), do: Timex.days_in_month(y, m)
def days_in_month(_), do: {:error, :invalid_date}
def week_of_month({y, m, d}) when is_date(y, m, d), do: Timex.week_of_month(y, m, d)
def week_of_month({{y, m, d}, _}) when is_date(y, m, d), do: Timex.week_of_month(y, m, d)
def week_of_month(_), do: {:error, :invalid_date}
def weekday({y, m, d} = date) when is_date(y, m, d), do: :calendar.day_of_the_week(date)
def weekday({{y, m, d} = date, _}) when is_date(y, m, d), do: :calendar.day_of_the_week(date)
def weekday(_), do: {:error, :invalid_date}
def weekday({y, m, d}, weekstart) when is_date(y, m, d),
do: Timex.Date.day_of_week(Timex.Date.new!(y, m, d), weekstart)
def weekday({{y, m, d}, _}, weekstart) when is_date(y, m, d),
do: Timex.Date.day_of_week(Timex.Date.new!(y, m, d), weekstart)
def weekday(_, _), do: {:error, :invalid_date}
def day({y, m, d} = date) when is_date(y, m, d),
do: 1 + Timex.diff(date, {y, 1, 1}, :days)
def day({{y, m, d} = date, _}) when is_date(y, m, d),
do: 1 + Timex.diff(date, {y, 1, 1}, :days)
def day(_), do: {:error, :invalid_date}
def is_valid?({y, m, d}) when is_date(y, m, d), do: true
def is_valid?({{y, m, d}, {h, mm, s}}) when is_datetime(y, m, d, h, mm, s), do: true
def is_valid?({{y, m, d}, {h, mm, s, _us}}) when is_datetime(y, m, d, h, mm, s), do: true
def is_valid?(_), do: false
def iso_week({y, m, d}) when is_date(y, m, d),
do: Timex.iso_week(y, m, d)
def iso_week({{y, m, d}, _}) when is_date(y, m, d),
do: Timex.iso_week(y, m, d)
def iso_week(_), do: {:error, :invalid_date}
def from_iso_day({y, m, d}, day) when is_day_of_year(day) and is_date(y, m, d) do
{year, month, day_of_month} = Timex.Helpers.iso_day_to_date_tuple(y, day)
{year, month, day_of_month}
end
def from_iso_day({{y, m, d}, {_, _, _} = time}, day)
when is_day_of_year(day) and is_date(y, m, d) do
{year, month, day_of_month} = Timex.Helpers.iso_day_to_date_tuple(y, day)
{{year, month, day_of_month}, time}
end
def from_iso_day({{y, m, d}, {_, _, _, _} = time}, day)
when is_day_of_year(day) and is_date(y, m, d) do
{year, month, day_of_month} = Timex.Helpers.iso_day_to_date_tuple(y, day)
{{year, month, day_of_month}, time}
end
def from_iso_day(_, _), do: {:error, :invalid_date}
def set({y, m, d} = date, options) when is_date(y, m, d),
do: do_set({date, {0, 0, 0}}, options, :date)
def set({{y, m, d}, {h, mm, s}} = datetime, options) when is_datetime(y, m, d, h, mm, s),
do: do_set(datetime, options, :datetime)
def set({{y, m, d}, {h, mm, s, us}}, options) when is_datetime(y, m, d, h, mm, s) do
{date, {h, mm, s}} = do_set({{y, m, d}, {h, mm, s}}, options, :datetime)
{date, {h, mm, s, us}}
end
def set(_, _), do: {:error, :invalid_date}
defp do_set(date, options, datetime_type) do
validate? = Keyword.get(options, :validate, true)
Enum.reduce(options, date, fn
_option, {:error, _} = err ->
err
option, result ->
case option do
{:validate, _} ->
result
{:datetime, {{_, _, _} = date, {_, _, _} = time} = dt} ->
if validate? do
case datetime_type do
:date ->
Timex.normalize(:date, date)
:datetime ->
{Timex.normalize(:date, date), Timex.normalize(:time, time)}
end
else
case datetime_type do
:date -> date
:datetime -> dt
end
end
{:date, {_, _, _} = d} ->
if validate? do
case result do
{_, _, _} -> Timex.normalize(:date, d)
{{_, _, _}, {_, _, _} = t} -> {Timex.normalize(:date, d), t}
end
else
case result do
{_, _, _} -> d
{{_, _, _}, {_, _, _} = t} -> {d, t}
end
end
{:time, {_, _, _} = t} ->
if validate? do
case result do
{_, _, _} -> date
{{_, _, _} = d, {_, _, _}} -> {d, Timex.normalize(:time, t)}
end
else
case result do
{_, _, _} -> date
{{_, _, _} = d, {_, _, _}} -> {d, t}
end
end
{:day, d} ->
if validate? do
case result do
{y, m, _} -> {y, m, Timex.normalize(:day, {y, m, d})}
{{y, m, _}, {_, _, _} = t} -> {{y, m, Timex.normalize(:day, {y, m, d})}, t}
end
else
case result do
{y, m, _} -> {y, m, d}
{{y, m, _}, {_, _, _} = t} -> {{y, m, d}, t}
end
end
{:year, year} ->
if validate? do
case result do
{_, m, d} -> {Timex.normalize(:year, year), m, d}
{{_, m, d}, {_, _, _} = t} -> {{Timex.normalize(:year, year), m, d}, t}
end
else
case result do
{_, m, d} -> {year, m, d}
{{_, m, d}, {_, _, _} = t} -> {{year, m, d}, t}
end
end
{:month, month} ->
if validate? do
case result do
{y, _, d} ->
{y, Timex.normalize(:month, month), Timex.normalize(:day, {y, month, d})}
{{y, _, d}, {_, _, _} = t} ->
{{y, Timex.normalize(:month, month), Timex.normalize(:day, {y, month, d})}, t}
end
else
case result do
{y, _, d} -> {y, month, d}
{{y, _, d}, {_, _, _} = t} -> {{y, month, d}, t}
end
end
{:hour, hour} ->
if validate? do
case result do
{_, _, _} -> result
{{_, _, _} = d, {_, m, s}} -> {d, {Timex.normalize(:hour, hour), m, s}}
end
else
case result do
{_, _, _} -> result
{{_, _, _} = d, {_, m, s}} -> {d, {hour, m, s}}
end
end
{:minute, min} ->
if validate? do
case result do
{_, _, _} -> result
{{_, _, _} = d, {h, _, s}} -> {d, {h, Timex.normalize(:minute, min), s}}
end
else
case result do
{_, _, _} -> result
{{_, _, _} = d, {h, _, s}} -> {d, {h, min, s}}
end
end
{:second, sec} ->
if validate? do
case result do
{_, _, _} -> result
{{_, _, _} = d, {h, m, _}} -> {d, {h, m, Timex.normalize(:second, sec)}}
end
else
case result do
{_, _, _} -> result
{{_, _, _} = d, {h, m, _}} -> {d, {h, m, sec}}
end
end
{name, _} when name in [:timezone, :microsecond] ->
result
{option_name, _} ->
{:error, {:bad_option, option_name}}
end
end)
end
def shift(date, [{_, 0}]),
do: date
def shift({y, m, d} = date, options) when is_date(y, m, d),
do: do_shift(date, options, :date)
def shift({{y, m, d}, {h, mm, s}} = datetime, options) when is_datetime(y, m, d, h, mm, s),
do: do_shift(datetime, options, :datetime)
def shift({{y, m, d}, {h, mm, s, _us}} = datetime, options) when is_datetime(y, m, d, h, mm, s),
do: do_shift(datetime, options, :datetime)
def shift(_, _), do: {:error, :invalid_date}
defp to_erl_datetime({y, m, d} = date) when is_date(y, m, d),
do: {:ok, {date, {0, 0, 0}}}
defp to_erl_datetime({{y, m, d}, {h, mm, s}} = dt) when is_datetime(y, m, d, h, mm, s),
do: {:ok, dt}
defp to_erl_datetime({{y, m, d}, {h, mm, s, _us}}) when is_datetime(y, m, d, h, mm, s),
do: {:ok, {{y, m, d}, {h, mm, s}}}
defp to_erl_datetime(_),
do: {:error, :invalid_date}
defp get_microseconds({_, _, _, us}) when is_integer(us),
do: us
defp get_microseconds({_, _, _, {us, _precision}}) when is_integer(us),
do: us
defp get_microseconds({_, _, _}),
do: 0
defp get_microseconds({date, time}) when is_tuple(date) and is_tuple(time),
do: get_microseconds(time)
defp do_shift(date, options, type) do
allowed_options =
Enum.reject(options, fn
{:weeks, _} ->
false
{:days, _} ->
false
{:hours, value} when value >= 24 or value <= -24 ->
true
{:hours, _} ->
false
{:minutes, value} when value >= 24 * 60 or value <= -24 * 60 ->
true
{:minutes, _} ->
false
{:seconds, value} when value >= 24 * 60 * 60 or value <= -24 * 60 * 60 ->
true
{:seconds, _} ->
false
{:milliseconds, value}
when value >= 24 * 60 * 60 * 1000 or value <= -24 * 60 * 60 * 1000 ->
true
{:milliseconds, _} ->
false
{:microseconds, {value, _}}
when value >= 24 * 60 * 60 * 1000 * 1000 or value <= -24 * 60 * 60 * 1000 * 1000 ->
true
{:microseconds, value}
when value >= 24 * 60 * 60 * 1000 * 1000 or value <= -24 * 60 * 60 * 1000 * 1000 ->
true
{:microseconds, _} ->
false
{_type, _value} ->
true
end)
case Timex.shift(to_naive_datetime(date), allowed_options) do
{:error, _} = err ->
err
%NaiveDateTime{} = nd when type == :date ->
{nd.year, nd.month, nd.day}
%NaiveDateTime{} = nd when type == :datetime ->
{{nd.year, nd.month, nd.day}, {nd.hour, nd.minute, nd.second}}
end
end
end