Would be nice to have this in the standard Elixir library, but we don't.
Date.add(date, n, :month) # where n could be +/-
How would you implement this?
This looks like a good starting point: https://stackoverflow.com/a/53407676/44080
Would be nice to have this in the standard Elixir library, but we don't.
Date.add(date, n, :month) # where n could be +/-
How would you implement this?
This looks like a good starting point: https://stackoverflow.com/a/53407676/44080
You could use the Timex implementation:
defp shift_by(%NaiveDateTime{:year => year, :month => month} = datetime, value, :months) do
m = month + value
shifted =
cond do
m > 0 ->
years = div(m - 1, 12)
month = rem(m - 1, 12) + 1
%{datetime | :year => year + years, :month => month}
m <= 0 ->
years = div(m, 12) - 1
month = 12 + rem(m, 12)
%{datetime | :year => year + years, :month => month}
end
# If the shift fails, it's because it's a high day number, and the month
# shifted to does not have that many days. This will be handled by always
# shifting to the last day of the month shifted to.
case :calendar.valid_date({shifted.year,shifted.month,shifted.day}) do
false ->
last_day = :calendar.last_day_of_the_month(shifted.year, shifted.month)
cond do
shifted.day <= last_day ->
shifted
:else ->
%{shifted | :day => last_day}
end
true ->
shifted
end
end
Timex uses the MIT license, so you should be able to incorporate this in pretty much any project.
ex_cldr_calendars can also do basic date math for adding and subtracting years, quarters, months, weeks and days for any calendar that implements the Calendar behaviour.
iex> Cldr.Calendar.plus ~D[2019-03-31], :months, -1
~D[2019-02-28]
# The :coerce option determines whether to force an end
# of month date when the result of the operation is an invalid date
iex> Cldr.Calendar.plus ~D[2019-03-31], :months, -1, coerce: false
{:error, :invalid_date}
Without adding a dependency like Timex, the following works for adding/subtracting Gregorian months without too much trouble - assuming you only need the first of each month. Shifting to a day of the month directly may be best served through a library, given how many calendrical fallacies there are.
defmodule DateUtils
@doc """
Shift a given date forward or back n months
"""
def shift_n_months(date, n) when n < 0, do: subtract_n_months(date, -1 * n)
def shift_n_months(date, n), do: add_n_months(date, n)
def add_n_months(date, 0), do: Date.beginning_of_month(date)
def add_n_months(date, n) do
date
|> Date.end_of_month()
|> Date.add(1)
|> add_n_months(n - 1)
end
def subtract_n_months(date, 0), do: Date.beginning_of_month(date)
def subtract_n_months(date, n) do
date
|> Date.beginning_of_month()
|> Date.add(-1)
|> subtract_n_months(n - 1)
end
end
There is an elixir function Date.add/2. Give it any date and it will add the dates for you.
iex>Date.add(~D[2000-01-03], -2)
~D[2000-01-01]
If you want to create the date to add to then i suggest you use the Date.new/4
iex>{:ok, date} = Date.new(year, month, day)
iex>date |> Date.add(n)