Resolve ambiguous date comparisons with possible missing return?

Opinion:

Some Date comparisons are, in certain contexts, ambiguous. The Julia Dates package refrains from defining operations where these operations are ambiguous. For example:

using Dates 

max(Quarter(1), Month(1)) # returns Month(1), as expected 
max(Day(1), Month(1)) # throws exception, probably because there exist comparisons, like max(Day(30), Month(1)), which are ambiguous

But why not define

Base.isless(x::Day, y::Year) = x.value < 365*y.value ?  true : (x.value > 366*y.value  ? false : missing) # type piracy 

Alternatively, throw an error instead of returning a missing.

This would be especially useful when people want to take into account frequency.

Why it would be useful:
Throwaway pseudo code, but suppose we have something like

struct TimeSeries{T, STEP} where {T, STEP} 
   data::AbstractVector 
   sr::StepRange 
end 
TimeSeries(av::AbstractVector{T}, sr::StepRange) where T= TimeSeries{T, sr.step}(av, sr)

And we want to conveniently compute covariances between variables at different frequencies. Suppose one is daily and one is monthly. Default fallback might take the lowest frequency step and aggregate/subsample from the higher frequency data. Determining which is lower frequency could be handled by max(ts1.sr.step, ts2.sr.step) – except this is not defined for some pairs of DatePeriod types.

Similarly, suppose you want to be able to subsample a quarterly frequency steprange from a monthly timeseries. That can be accomodated by max. But If you build this syntax want to subsample from Date(Year(2010)):Date(Year(2020)), this defaults to a Day(1) stepsize, which cannot be compared with a month.

As a user, this is not hard to deal with but it is annoying, and it would be obviated by comparisons that give the correct answer when the comparison is unambiguous. I’ve done type piracy to get around this, but type piracy is generally not a good idea.

Thanks!

On one of those “falsehoods programmers believe about time” lists I see

  • Years have 365 or 366 days.

I wish these lists would provide the counterexamples to prove their point but this one doesn’t so idk if it’s right.

There is also something nice about having functions that always return a value (never fail). Returning missing would make it type-unstable.

Only marginally related, but on the topic of addresses some time ago I came across a similar list, Falsehoods programmers believe about addresses, which does have counterexamples.

If I have to make a guess, it could refer to the fact the years on which there was the switch from the Julian to the Gregorian calendar, which is different country by country, had 10-13 days less, depending on when the switch happened.

2 Likes

Okay, then we write

Base.isless(x::Day, y::Year) = 
    x.value < 365*y.value - 13 ? true : 
                                 (x.value > 366*y.value ? false : 
                                                          throw(AssertionError("Ambiguous DatePeriod comparison"))) # type piracy