Current section
Files
Jump to
Current section
Files
lib/chart/utils.ex
defmodule Contex.Utils do
@moduledoc false
def date_compare(%DateTime{} = a, %DateTime{} = b) do
DateTime.compare(a, b)
end
def date_compare(%NaiveDateTime{} = a, %NaiveDateTime{} = b) do
NaiveDateTime.compare(a, b)
end
def date_diff(%DateTime{} = a, %DateTime{} = b, unit), do: DateTime.diff(a, b, unit)
def date_diff(%NaiveDateTime{} = a, %NaiveDateTime{} = b, unit),
do: NaiveDateTime.diff(a, b, unit)
@doc """
Adds intervals to dates. Note that only system time units (nanosecond, microsecond, millisecond, second) are
supported by default by DateTime and NaiveDateTime. Minutes, Hours, Days & Weeks need to be converted to one
of the supported units. Special cases have been introduced for :months & :years due to variable days in month
and leap year behaviour. This has been copied from Timex.
iex> {:ok, d1, 0} = DateTime.from_iso8601("2016-01-31T03:00:00Z")
{:ok, ~U[2016-01-31 03:00:00Z], 0}
iex> d1 = Contex.Utils.date_add(d1, 1, :months)
~U[2016-02-29 03:00:00Z]
iex> _d1 = Contex.Utils.date_add(d1, 1, :years)
~U[2017-02-28 03:00:00Z]
iex> {:ok, d1, 0} = DateTime.from_iso8601("2016-03-31T03:00:00Z")
{:ok, ~U[2016-03-31 03:00:00Z], 0}
iex> d1 = Contex.Utils.date_add(d1, -1, :months)
~U[2016-02-29 03:00:00Z]
iex> _d1 = Contex.Utils.date_add(d1, -1, :years)
~U[2015-02-28 03:00:00Z]
"""
def date_add(dt, amount_to_add, :years), do: shift_by(dt, amount_to_add, :years)
def date_add(dt, amount_to_add, :months) do
new_date = shift_by(dt, amount_to_add, :months)
if is_last_day_of_month(dt) do
ldom_new = :calendar.last_day_of_the_month(new_date.year, new_date.month)
%{new_date | day: ldom_new}
else
new_date
end
end
def date_add(%DateTime{} = dt, amount_to_add, unit), do: DateTime.add(dt, amount_to_add, unit)
def date_add(%NaiveDateTime{} = dt, amount_to_add, unit),
do: NaiveDateTime.add(dt, amount_to_add, unit)
defp is_last_day_of_month(%{year: year, month: month, day: day}) do
:calendar.last_day_of_the_month(year, month) == day
end
defp date_min(a, b), do: if(date_compare(a, b) == :lt, do: a, else: b)
defp date_max(a, b), do: if(date_compare(a, b) != :lt, do: a, else: b)
def safe_min(nil, nil), do: nil
def safe_min(nil, b), do: b
def safe_min(a, ""), do: a
def safe_min("", b), do: b
def safe_min(a, nil), do: a
def safe_min(%DateTime{} = a, %DateTime{} = b), do: date_min(a, b)
def safe_min(%NaiveDateTime{} = a, %NaiveDateTime{} = b), do: date_min(a, b)
def safe_min(a, b) when is_number(a) and is_number(b), do: min(a, b)
def safe_min(_, _), do: nil
def safe_max(nil, nil), do: nil
def safe_max(nil, b), do: b
def safe_max(a, nil), do: a
def safe_max("", b), do: b
def safe_max(a, ""), do: a
def safe_max(%DateTime{} = a, %DateTime{} = b), do: date_max(a, b)
def safe_max(%NaiveDateTime{} = a, %NaiveDateTime{} = b), do: date_max(a, b)
def safe_max(a, b) when is_number(a) and is_number(b), do: max(a, b)
def safe_max(_, _), do: nil
# def safe_min(x, y), do: safe_combine(x, y, fn x, y -> min(x, y) end)
# def safe_max(x, y), do: safe_combine(x, y, fn x, y -> max(x, y) end)
def safe_add(x, y), do: safe_combine(x, y, fn x, y -> x + y end)
defp safe_combine(x, y, combiner) when is_number(x) and is_number(y), do: combiner.(x, y)
defp safe_combine(x, _, _) when is_number(x), do: x
defp safe_combine(_, y, _) when is_number(y), do: y
defp safe_combine(_, _, _), do: nil
def fixup_value_range({min, max}) when min == max and max > 0, do: {0, max}
def fixup_value_range({min, max}) when min == max and max < 0, do: {max, 0}
def fixup_value_range({0, 0}), do: {0, 1}
def fixup_value_range({0.0, 0.0}), do: {0.0, 1.0}
def fixup_value_range({min, max}), do: {min, max}
# DateTime shifting methods copied from `defimpl Timex.Protocol, for: DateTime`
# License Details:
# The MIT License (MIT)
## Copyright (c) 2016 Paul Schoenfelder
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
defp shift_by(%{year: y} = datetime, value, :years) do
shifted = %{datetime | year: y + value}
# If a plain shift of the year fails, then it likely falls on a leap day,
# so set the day to the last day of that month
case :calendar.valid_date({shifted.year, shifted.month, shifted.day}) do
false ->
last_day = :calendar.last_day_of_the_month(shifted.year, shifted.month)
%{shifted | day: last_day}
true ->
shifted
end
end
defp shift_by(%{} = datetime, 0, :months), do: datetime
# Positive shifts
defp shift_by(%{year: year, month: month, day: day} = datetime, value, :months)
when value > 0 do
if month + value <= 12 do
ldom = :calendar.last_day_of_the_month(year, month + value)
if day > ldom do
%{datetime | month: month + value, day: ldom}
else
%{datetime | month: month + value}
end
else
diff = 12 - month + 1
shift_by(%{datetime | year: year + 1, month: 1}, value - diff, :months)
end
end
# Negative shifts
defp shift_by(%{year: year, month: month, day: day} = datetime, value, :months) do
cond do
month + value >= 1 ->
ldom = :calendar.last_day_of_the_month(year, month + value)
if day > ldom do
%{datetime | month: month + value, day: ldom}
else
%{datetime | month: month + value}
end
:else ->
shift_by(%{datetime | year: year - 1, month: 12}, value + month, :months)
end
end
end