We currently have `add/2-3` to manipulate calendar types in the standard library. These functions allow adding a specified amount of time of given unit to a date/time. The standard library currently misses means to apply more complex, or logical
to calendar types. e.g. adding a month, a week, or one month and 10 days to a date.
Reasons for it
While similar functionality exists in libraries, such as CLDR, Timex, Tox, adding this functionality to the standard library has already been requested and discussed at multiple occasions over the past years. To list a few examples:
Furthermore the shift behaviour in the extremely popular library Timex
changed in Elixir >= 1.14.3 which may have complicated the mostly lean and non-breaking language upgrade Elixir has to offer.
Elixir has a great set of modules and functions that deal with date and time, the APIs are consistent and `shift/2-3` should fit right in, solving many standard needs of various industries, be it for reporting, appointments, events, finance... the list goes on, engineers probably face the need to shift time logically more often than not in their careers.
Technical details
Duration
A date or time must be shifted by a
duration. There is an
ISO8601 for durations, which the initial implementation is loosely following. The structure of a Duration lives in its own module with its own set of functions to create and manipulate durations. One example of where it diverts from the ISO standard, is that it implements microseconds. Microseconds in a
duration are stored in the same format as in the time calendar types, meaning they integrate well and provide consistency.
Shift
The shift behaviour is implemented as a callback on Calendar and supported by all calendar types: Date, DateTime, NaiveDateTime and Time. Date, Time and NaiveDateTime each have their own implementation of a "shift", while DateTime gets converted to a NaiveDateTime before applying the shift, and is then rebuilt to a DateTime in its original timezone. `shift/2-3` also has guaranteed output types (which isn't a given in many libraries) and follows the consistent API which is established in the calendar modules.
Benchmarks
There are some benchmarks + StreamData tests in the PR description.
Outlook
After adding the Duration type and shift behaviour to the standard library, the following things could be explored and derived from the initial work:
- Implementing a protocol that allows Duration to be applied to any data type, not just dates and times.
- A range-like data type that allows us to do recurring constructs on any data type. For example, Duration.interval(~D[2000-01-01], month: 1), when iterated, would emit {:ok, date} | {:error, start, duration, reason} entries
- A sigil for easy creation of durations: ~P[3 hours and 10 minutes]
- Making it so add/2-3 reuses the shift_* functions
Reasons against it
While I am convinced that adding `shift/2-3` to the standard library would be very beneficial, nothing really speaks against the points mentioned above to be implemented in a library instead. However, something as crucial and central as date/time manipulation should still be part of the standard library, negating the risk of breaking changes, inconsistent behaviour and outdated or too unique ergonomics which aren't widely applicable, unlike what should be part of the standard library.
Many thanks to @jose & @kip for the initial reviews and everyone in advance taking the time to read the proposal!
Looking forward to hear other peoples ideas and opinions on the subject!