There is currently a PR on the repo with very lively discussion about what the following operation should do:
using Dates
a = now()
b = a + Nanosecond(1)
(the situation is analogous with Microsecond
)
Currently, the result is always exactly equal to a
, because the DateTime
arithmetic implemented in Dates
simply truncates the non-DateTime
argument to milliseconds (as that is the supported precision of DateTime
), and whatever the result is, will be added to DateTime
:
julia> using Dates
julia> a = now()
2023-08-18T14:35:44.462
julia> a === a + Nanosecond(1)
true
julia> a === a + Microsecond(999)
true
julia> a === a + Microsecond(1000) # now we're at the millisecond scale
false
julia> (a + Microsecond(1000)) === (a + Microsecond(1999))
true
This has consequences when trying to accumulate a number of durations into a DateTime
, for example to figure out how long some continued measurements took, perhaps from samples of an oscilloscope:
julia> a = now()
julia> reduce(+, fill(Microsecond(1234), 10000), init=a)
2023-08-18T14:22:02.527
julia> a + reduce(+, fill(Microsecond(1234), 10000), init=Nanosecond(0))
2023-08-18T14:22:04.867
This, where two different forms of accumulation lead to different results, happens because in the first form due to the repeated truncation there is an accumulation of error, and thus the result will not be as exact as it could be. In essence, DateTime + Nanosecond
or DateTime + Microsecond
are not associative. This is problematic, because the dataloss is silent, and near impossible to catch after the fact - especially if you donāt have access to the original data anymore.
There are more or less three ways to resolve this:
- Keep the current behavior, and simply document that this kind of arithmetic does some form of rounding.
- Increase the precision of
DateTime
up toNanosecond
precision (and not support smaller scales at all, effectively pushing the failure case to higher precisions, but not solving it entirely). This would necessitate an increase in size ofDateTime
(from currently 64 bits to 128 bits, to accomodate the additional data needed to keep track of everything). - Deprecate the existing behavior, and direct users to use
round
/trunc
/ceil
/floor
on their too-small-to-fit-in-DateTime
calculations, to explicitly specify the behavior they want. Only arithmetic with exact results are allowed - the methods in question would be removed when (if ever) a breaking release in Julia happens.
Iām personally in favor of option 3), because itās my believe that a programming language (and standard library, targeting a very general use case) should have as few foot guns as possible, and should give hints & nudges towards correct usage for their application, with as few guesses about what the user cares about as possible.
Iād like to hear what the community thinks though, and in particular Iād like to hear from people who often use this kind of arithmetic and what theyād expect to happen in these circumstances.