defmodule Ash.Query.Function.Ago do
@moduledoc """
Subtracts the given interval from the current time in UTC.
For example:
deleted_at > ago(7, :day)
Documentation + available intervals inspired by the corresponding ecto interval implementation
"""
use Ash.Query.Function, name: :ago
def args, do: [[:integer, :duration_name]]
def evaluate(%{arguments: [factor, interval]}) do
now = DateTime.utc_now()
shifted = datetime_add(now, -factor, interval)
{:known, shifted}
end
@doc false
def datetime_add(datetime, amount_to_add, unit)
when unit in [:microsecond, :millisecond, :second] do
DateTime.add(datetime, amount_to_add, unit)
end
def datetime_add(datetime, amount_to_add, :minute) do
DateTime.add(datetime, amount_to_add * 60, :second)
end
def datetime_add(datetime, amount_to_add, :hour) do
DateTime.add(datetime, amount_to_add * 60 * 60, :second)
end
def datetime_add(datetime, amount_to_add, :day) do
date = Date.add(datetime, amount_to_add)
%{datetime | year: date.year, month: date.month, day: date.day}
end
def datetime_add(datetime, amount_to_add, :week) do
datetime_add(datetime, 7 * amount_to_add, :day)
end
def datetime_add(datetime, amount_to_add, :month) do
months_since_zero = datetime.year * 12 + datetime.month - 1 + amount_to_add
year = div(months_since_zero, 12)
{year, month} =
case rem(months_since_zero, 12) do
x when x < 0 -> {year - 1, 12 + x + 1}
x -> {year, x + 1}
end
# clamp to last day of month
last_day = Calendar.ISO.days_in_month(year, month)
day = min(datetime.day, last_day)
%{datetime | year: year, month: month, day: day}
end
def datetime_add(%{month: month, day: day} = datetime, amount_to_add, :year) do
result = %{datetime | year: datetime.year + amount_to_add}
# Adjust 29-Feb to 01-March when not a leap year
if month == 2 and day == 29 and not Calendar.ISO.leap_year?(result.year) do
%{result | month: 3, day: 1}
else
result
end
end
end