IntervalSets.jl icon indicating copy to clipboard operation
IntervalSets.jl copied to clipboard

findall for Date(Time) ranges

Open jagot opened this issue 5 years ago • 1 comments

Many thanks for getting this done.

I just found another edge case, which I'm not really sure this ought to try to support, but anyway here it is:

julia> using IntervalSets                                          

julia> using Dates                                                 

julia> findall(in(Interval(Date(2020, 1, 8), Date(2020, 1, 22))), Date(2020):Week(1):Date(2021))                                      
ERROR: MethodError: no method matching isless(::Week, ::Int64)
Closest candidates are:
  isless(::Missing, ::Any) at missing.jl:87
  isless(::DoubleFloats.DoubleFloat{T}, ::Integer) where T<:Union{Float16, Float32, Float64} at /Users/me/.julia/packages/DoubleFloats/8fO4c/src/type/compare.jl:135
  isless(::AbstractFloat, ::Real) at operators.jl:158
  ...
Stacktrace:
 [1] <(::Week, ::Int64) at ./operators.jl:268
 [2] findall(::Base.Fix2{typeof(in),Interval{:closed,:closed,Date}}, ::StepRange{Date,Week}) at /Users/me/.julia/packages/IntervalSets/D282g/src/findall.jl:43

Originally posted by @mcabbott in https://github.com/JuliaMath/IntervalSets.jl/pull/63#issuecomment-631140661

jagot avatar Jun 01 '20 13:06 jagot

This ought to be an easy fix along the lines of

function Base.findall(interval_d::Base.Fix2{typeof(in),Interval{L,R,T}}, x::AbstractRange)  where {L,R,T}
    isempty(x) && return 1:0

    interval = interval_d.x
    il, ir = firstindex(x), lastindex(x)
    δx = step(x)
    a,b = if δx < zero(δx)
        rev = findall(in(interval), reverse(x))
        isempty(rev) && return rev

        a = (il+ir)-last(rev)
        b = (il+ir)-first(rev)

        a,b
    else
        lx, rx = first(x), last(x)
        l = max(leftendpoint(interval), lx-oneunit(δx))
        r = min(rightendpoint(interval), rx+oneunit(δx))

        (l > rx || r < lx) && return 1:0

        a = il + max(0, round(Int, cld(l-lx, δx)))
        a += (a ≤ ir && (x[a] == l && L == :open || x[a] < l))

        b = min(ir, round(Int, cld(r-lx, δx)) + il)
        b -= (b ≥ il && (x[b] == r && R == :open || x[b] > r))

        a,b
    end
    # Reversing a range could change sign of values close to zero (cf
    # sign of the smallest element in x and reverse(x), where x =
    # range(BigFloat(-0.5),stop=BigFloat(1.0),length=10)), or more
    # generally push elements in or out of the interval (as can cld),
    # so we need to check once again.
    a += +(a < ir && x[a] ∉ interval) - (il < a && x[a-1] ∈ interval)
    b += -(il < b && x[b] ∉ interval) + (b < ir && x[b+1] ∈ interval)

    a:b
end

but we get this error:

julia> i = Interval(Date(2020, 1, 8), Date(2020, 1, 22))
2020-01-08..2020-01-22

julia> x = Date(2020):Week(1):Date(2021)
2020-01-01:1 week:2020-12-30

julia> findall(in(i), x)
ERROR: MethodError: no method matching div(::Day, ::Week, ::RoundingMode{:Up})
Closest candidates are:
  div(::P, ::P, ::RoundingMode) where P<:Period at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/Dates/src/periods.jl:80
  div(::P, ::Real, ::RoundingMode) where P<:Period at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/Dates/src/periods.jl:81
  div(::Any, ::Any) at div.jl:37
  ...
Stacktrace:
 [1] cld(::Day, ::Week) at ./div.jl:99
 [2] findall(::Base.Fix2{typeof(in),Interval{:closed,:closed,Date}}, ::StepRange{Date,Week}) at /Users/jagot/.julia/dev/IntervalSets/src/findall.jl:60
 [3] top-level scope at REPL[16]:1
 [4] eval(::Module, ::Any) at ./boot.jl:331
 [5] eval_user_input(::Any, ::REPL.REPLBackend) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:86
 [6] run_backend(::REPL.REPLBackend) at /Users/jagot/.julia/packages/Revise/MgvIv/src/Revise.jl:1023
 [7] top-level scope at REPL[3]:0

This happens because l-lx becomes days, and cld is not defined for dividing days by weeks.

jagot avatar Jun 01 '20 13:06 jagot

This issue was already solved in the current master branch.

julia> using IntervalSets

julia> using Dates

julia> findall(in(Interval(Date(2020, 1, 8), Date(2020, 1, 22))), Date(2020):Week(1):Date(2021))   
2:4

hyrodium avatar Sep 28 '22 07:09 hyrodium