A simple inference bug with Dates.Period?

In some simple arithmetic subtracting types with Dates.CompoundPeriod, a bug arises when you try to subtract a Dates.CompoundPeriod that is equal to 0. However, it does not seem to be a problem with the Dates code but rather an inference bug that occurs when broadcasting a Base.- over an empty vector of type Dates.Period.

julia> using Dates

julia> -Int[]
Int64[]

julia> -Dates.Period[]
Any[]

julia> t1 = Dates.Day(0)+Dates.Hour(0)
empty period

julia> t1.periods
Period[]

julia> -t1
ERROR: MethodError: no method matching Dates.CompoundPeriod(::Vector{Any})
Closest candidates are:
  Dates.CompoundPeriod(::Vector{Period}) at /Applications/Julia-1.7x86.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Dates/src/periods.jl:167
  Dates.CompoundPeriod(::Vector{<:Period}) at /Applications/Julia-1.7x86.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Dates/src/periods.jl:230
  Dates.CompoundPeriod(::Time) at /Applications/Julia-1.7x86.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Dates/src/periods.jl:232
  ...
Stacktrace:
 [1] -(x::Dates.CompoundPeriod)
   @ Dates /Applications/Julia-1.7x86.app/Contents/Resources/julia/share/julia/stdlib/v1.7/Dates/src/periods.jl:368
 [2] top-level scope
   @ REPL[172]:1

On second thought, perhaps this is the intended broadcast behavior. In this case, the output type is again not as strict as possible:

julia> T = Union{Int, Float64}
Union{Float64, Int64}

julia> v = T[]
Union{Float64, Int64}[]

julia> -v
Real[]

In either case, the arithmetic issues in Dates could be easily fixed by special casing the empty vector in the Dates code:

(-)(x::CompoundPeriod) = CompoundPeriod(-x.periods)

could be replaced by

function (-)(x::CompoundPeriod)
    if isempty(x.periods)
        return CompoundPeriod(Dates.Period[])
    end
    return CompoundPeriod(-x.periods)
end
1 Like

You are on to something.
Currently empty - notempty works but notempty - empty errors, that is a bug which your suggestion fixes (its probably ok to return x itself if empty).
I have run into the lack of iteration over CompoundPeriods, which is easy enough, and handles the empty case:

Base.iterate(x::Dates.CompoundPeriod) = 
    iterate(x.periods, 1)
Base.iterate(x::Dates.CompoundPeriod, state) =
    iterate(x.periods, state)