julia> (1e16 + 1) + 1 1.0e16
julia> 1e16 + (1 + 1) 1.0000000000000002e16
I think the fundamental issue with dates, however, is deeper than "addition is non-associative". It is that "1 month" is a context-sensitive duration (so is a "1 day", due to leap seconds).
I am curious about using intervals. If "{Year} January (no day)" had a representation as "Jan. 1 - Jan. 31", then "Jan. 1 -- Jan. 31" + "1 month" = "{Year} Feb. 1 -- Feb. 28" (or 29 if the {Year} is a leap year".
Seems to me best practice of renewing accounts is to always apply padding (TODO: renew Foo today because it expires in roughly n days); if you make as a minimum n >= 2 (or how about >=3 as a safety factor) then you don't have to consider how the account provider administers their cut-off (expires at 00:00 vs 23:59, is timezone a factor?, etc).
With this approach the difference between 'instantaneous time' and 'calendar time' are, for practical purposes, a moot point.
So the date I enter into the KeepassXC field has the padding included. The expiry date as described by the provider I can put in the note field for additional reference.
The correct solution in my opinion would be to have distinct types for this sort of thing to help clear up the ambiguity. Date and time are really tricky concepts to model though and it is a difficult balance between honesty/precision and intuitiveness for such APIs. One such clunkiness I've seen with KeepassXC is that it stores password expirations exclusively as timestamps. This is actually not correct for the common use case of passwords expiring on a particular day (since you don't know the time) because what if you are in a different timezone? This I think shows that you can't just convert dates into datetimes without problems.