The dates that a date plus or minus a duration can be (D-109).
The run adds a duration to a date in two ways (Feel.Runtime.Temporal):
| The duration | The date it gives |
|---|---|
years and months duration | the months added to the month of the date, with the day cut to the length of that month: 2021-01-31 plus P1M is 2021-02-28 |
days and time duration | the whole days of the seconds added, rounded down (floor(seconds / 86400)): 36 hours are a day, and minus 1 second is minus a day |
Both are null when the year leaves the years of the calendar, and a subtraction is the addition of the negative.
Both do not decrease when the date does, and when the months or the seconds do not, so the dates that an interval
of days and an interval of amounts can give lie between the date of the first and the date of the last, and
shift/4 is that, for the intervals of the two sets.
For a days and time duration the dates in between are all there (a day after a day is the next day), and the set is exact. For a years and months duration they are not: the 29th of February less 25 months is the 29th of January, and the 1st of March less 25 months is the 1st of February, so the 30th and the 31st of January are not given. Within a month of the dates the days are a day each, to the length of the month they come to, so each month of the dates and each month of the duration is a piece (up to 2,000 pieces), and the set is exact; past that it is the one interval from the first to the last, which holds more and so is sound.
The result is null exactly when the first or the last date of a piece, as the calendar would have it with no limit, is outside the years of the calendar, since those are dates the run can be given; and a date that is outside is not in the set.
iex> alias Feel.Type.NumberSet
iex> day = fn date -> Decimal.new(Date.to_gregorian_days(date)) end
iex> january = NumberSet.interval(day.(~D[2021-01-01]), day.(~D[2021-01-31]))
iex> month = NumberSet.point(Decimal.new(1))
iex> {dates, false} = Feel.Analysis.DateArithmetic.shift(january, :years_and_months_duration, month, :add)
iex> Feel.Type.Union.format([{:date, dates}])
"date in [2021-02-01..2021-02-28]"
Summary
Functions
The days that a date of the set of days plus (:add) or minus (:subtract) a duration of the set of amounts can be,
and whether the result can be null (the year can be out of the calendar). It is :error if either set has nothing
that a value can be (an interval of less than a day, a set with no whole month), and the caller says what it did.
Functions
@spec shift( Feel.Type.NumberSet.t(), :years_and_months_duration | :days_and_time_duration, Feel.Type.NumberSet.t(), :add | :subtract ) :: {Feel.Type.NumberSet.t(), boolean()} | :error
The days that a date of the set of days plus (:add) or minus (:subtract) a duration of the set of amounts can be,
and whether the result can be null (the year can be out of the calendar). It is :error if either set has nothing
that a value can be (an interval of less than a day, a set with no whole month), and the caller says what it did.