Introduction
A function that adds months to a date (without overflowing ends of months) is implemented in many languages/packages. In Teradata SQL it's ADD_MONTHS, here are some examples:
ADD_MONTHS('2021-01-31', 1) => 2021-02-28
ADD_MONTHS('2021-01-30', 1) => 2021-02-28
ADD_MONTHS('2021-02-28', 1) => 2021-03-28
ADD_MONTHS('2021-02-28', -12) => 2020-02-28
Teradata SQL has also a function that goes a step further, namely OADD_MONTHS. Here, when given an end-of-month date, it always returns an end-of-month date.
To illustrate the difference:
ADD_MONTHS('2021-02-28', 1) => 2021-03-28
OADD_MONTHS('2021-02-28', 1) => 2021-03-31
The task
You are given a date and an integer. Your output should mimic the behaviour of OADD_MONTHS described above.
Any reasonable input/output form is acceptable (including your language's native date/datetime type, a string, number of days/seconds from a fixed point, etc.)
You may assume the input and target dates are after 1600-01-01
and the date is well defined (so no 2021-03-32
). You may use the Georgian calendar or any similar calendar implementing standard month lengths and taking into account leap years.
If you have a builtin specifically for this, consider including a non-builtin answer as well to make your answer more interesting.
Test cases
Date , offset => output (explanation)
2021-01-31, 0 => 2021-01-31 (boring)
2021-01-31, 1 => 2021-02-28 (no overflow)
2020-12-31, 1 => 2021-01-31 (next year)
2020-12-07, 1 => 2021-01-07 (next year)
2021-01-31, -1 => 2020-12-31 (previous year)
2021-01-30, -1 => 2020-12-30 (previous year)
2021-01-01, -1 => 2020-12-01 (previous year)
2020-12-30, 2 => 2021-02-28 (no overflow)
2021-02-28, 1 => 2021-03-31 (end-of-month -> end-of-month)
2021-09-30, 1 => 2021-10-31 (end-of-month -> end-of-month)
2021-02-28, -12 => 2020-02-29 (end-of-month -> end-of-month)
2020-02-28, 1 => 2020-03-28 (leap year - 28.02 is not end-of-month)
1995-02-28, -1140 => 1900-02-28 (not a leap year)