From eeb02a4e7b672f55c421069ce002083a2a5b59f6 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 7 Mar 2018 16:32:55 -0600 Subject: [PATCH] AnchoredInterval refactored to use span as field --- README.md | 10 ++- docs/src/index.md | 10 ++- src/anchoredinterval.jl | 157 +++++++++++++++++++++++++++------------ src/description.jl | 12 +-- src/endpoint.jl | 3 + test/anchoredinterval.jl | 145 ++++++++++++++++++++---------------- 6 files changed, 211 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 46ed994c..ca315e64 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,12 @@ This package defines: * `AbstractInterval`, along with its subtypes: * `Interval{T}`, which represents a non-iterable range between two endpoints of type `T` - * `AnchoredInterval{T, P}`, which represents a non-iterable range defined by a single - value `anchor::T` and the value type `P` which represents the size of the range - * `HourEnding`, a type alias for `AnchoredInterval{T, Hour(-1)}` - * `HourBeginning`, a type alias for `AnchoredInterval{T, Hour(1)}` + * `AnchoredInterval{T, S, E}`, which represents a non-iterable range defined by a single + value `anchor::T` and `span::S` which represents the size of the range. The type + parameter `E` is an instance of `Direction` and indicate whether the anchor is a + left-endpoint (`Beginning`) or a right-endpoing (`Ending`). + * `HourEnding`, a type alias for `AnchoredInterval{T, Hour, Ending}` + * `HourBeginning`, a type alias for `AnchoredInterval{T, Hour, Beginning}` * `HE` and `HB`, pseudoconstructors for `HourEnding` and `HourBeginning` that round the anchor up (`HE`) or down (`HB`) to the nearest hour * `Inclusivity`, which represents whether an `AbstractInterval` is open, half-open, or diff --git a/docs/src/index.md b/docs/src/index.md index 5c74de0e..e9707fc0 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -3,10 +3,12 @@ This package defines: * `AbstractInterval`, along with its subtypes: * `Interval{T}`, which represents a non-iterable range between two endpoints of type `T` - * `AnchoredInterval{T, P}`, which represents a non-iterable range defined by a single - value `anchor::T` and the value type `P` which represents the size of the range - * `HourEnding`, a type alias for `AnchoredInterval{T, Hour(-1)}` - * `HourBeginning`, a type alias for `AnchoredInterval{T, Hour(1)}` + * `AnchoredInterval{T, S, E}`, which represents a non-iterable range defined by a single + value `anchor::T` and `span::S` which represents the size of the range. The type + parameter `E` is an instance of `Direction` and indicate whether the anchor is a + left-endpoint (`Beginning`) or a right-endpoing (`Ending`). + * `HourEnding`, a type alias for `AnchoredInterval{T, Hour, Ending}` + * `HourBeginning`, a type alias for `AnchoredInterval{T, Hour, Beginning}` * `HE` and `HB`, pseudoconstructors for `HourEnding` and `HourBeginning` that round the anchor up (`HE`) or down (`HB`) to the nearest hour * `Inclusivity`, which represents whether an `AbstractInterval` is open, half-open, or diff --git a/src/anchoredinterval.jl b/src/anchoredinterval.jl index 7ffe7457..bc50e45f 100644 --- a/src/anchoredinterval.jl +++ b/src/anchoredinterval.jl @@ -1,11 +1,11 @@ using Base.Dates: value, coarserperiod """ - AnchoredInterval{T, P}(anchor::T, [inclusivity::Inclusivity]) -> AnchoredInterval{T, P} - AnchoredInterval{T, P}(anchor::T, [closed_left::Bool, closed_right::Bool]) -> AnchoredInterval{T, P} + AnchoredInterval{T, S, E}(anchor::T, [span::S, inclusivity::Inclusivity]) + AnchoredInterval{T, S, E}(anchor::T, [span::S, closed_left::Bool, closed_right::Bool]) - AnchoredInterval(anchor::T, P, [inclusivity::Inclusivity]) -> AnchoredInterval{T, P} - AnchoredInterval(anchor::T, P, [closed_left::Bool, closed_right::Bool]) -> AnchoredInterval{T, P} + AnchoredInterval{T, S, E}(anchor::T, [inclusivity::Inclusivity]) + AnchoredInterval{T, S, E}(anchor::T, [closed_left::Bool, closed_right::Bool]) `AnchoredInterval` is a subtype of `AbstractInterval` that represents a non-iterable range or span of values defined not by two endpoints but instead by a single `anchor` point and @@ -24,8 +24,9 @@ included for positive values of `P` and the greater endpoint included for negati range of values. This happens most often with dates and times, where "HE15" is often used as shorthand for (14:00..15:00]. -To this end, `HourEnding` is a type alias for `AnchoredInterval{T, Hour(-1)} where T`. -Similarly, `HourBeginning` is a type alias for `AnchoredInterval{T, Hour(1)} where T`. +To this end, `HourEnding` is a type alias for `AnchoredInterval{T, Hour, Ending} where T`. +Similarly, `HourBeginning` is a type alias for +`AnchoredInterval{T, Hour, Beginning} where T`. ### Rounding @@ -52,41 +53,83 @@ HourBeginning{DateTime}(2016-08-11T02:00:00, Inclusivity(true, false)) ```julia julia> AnchoredInterval(DateTime(2016, 8, 11, 12), Hour(-1)) -HourEnding{DateTime}(2016-08-11T12:00:00, Inclusivity(false, true)) +HourEnding{DateTime}(2016-08-11T12:00:00, -1 hour, Inclusivity(false, true)) julia> AnchoredInterval(DateTime(2016, 8, 11), Day(1)) -AnchoredInterval{DateTime, 1 day}(2016-08-11T00:00:00, Inclusivity(true, false)) +AnchoredInterval{DateTime, Day, Beginning}(2016-08-11T00:00:00, 1 day, Inclusivity(true, false)) julia> AnchoredInterval(DateTime(2016, 8, 11, 12, 30), Minute(5), true, true) -AnchoredInterval{DateTime, 5 minutes}(2016-08-11T12:30:00, Inclusivity(true, true)) +AnchoredInterval{DateTime, Minute, Beginning}(2016-08-11T12:30:00, 5 minutes, Inclusivity(true, true)) ``` See also: [`Interval`](@ref), [`Inclusivity`](@ref), [`HE`](@ref), [`HB`](@ref) """ -struct AnchoredInterval{T, P} <: AbstractInterval{T} +struct AnchoredInterval{T, S, E} <: AbstractInterval{T} anchor::T + span::S inclusivity::Inclusivity + + function AnchoredInterval{T, S, E}(anchor::T, span::S, inc::Inclusivity) where {T, S, E} + @assert E isa Direction + if span < zero(S) + @assert E == Ending + elseif span > zero(S) + @assert E == Beginning + else + @assert E isa Direction + end + @assert typeof(anchor + span) == T + new{T, S, E}(anchor, span, inc) + end end -# When an interval is anchored to the lesser endpoint, default to Inclusivity(false, true) -# When an interval is anchored to the greater endpoint, default to Inclusivity(true, false) -function AnchoredInterval{T, P}(i::T) where {T, P} - return AnchoredInterval{T, P}(i::T, Inclusivity(P ≥ zero(P), P ≤ zero(P))) +function AnchoredInterval{T, S, E}(anchor::T, span::S, x::Bool, y::Bool) where {T, S, E} + AnchoredInterval{T, S, E}(anchor, span, Inclusivity(x, y)) +end +function AnchoredInterval{T, S, E}(anchor::T, span::S) where {T, S, E} + # If an interval is anchored to the lesser endpoint, default to Inclusivity(false, true) + # If an interval is anchored to the greater endpoint, default to Inclusivity(true, false) + AnchoredInterval{T, S, E}(anchor, span, Inclusivity(span ≥ zero(S), span ≤ zero(S))) +end +function AnchoredInterval{T, S, E}(anchor::T, inc::Inclusivity) where {T, S, E} + span = E == Ending ? -oneunit(S) : oneunit(S) + AnchoredInterval{T, S, E}(anchor, span, inc) +end +function AnchoredInterval{T, S, E}(anchor::T, x::Bool, y::Bool) where {T, S, E} + AnchoredInterval{T, S, E}(anchor, Inclusivity(x, y)) +end +function AnchoredInterval{T, S, E}(anchor::T) where {T, S, E} + span = E == Ending ? -oneunit(S) : oneunit(S) + AnchoredInterval{T, S, E}(anchor, span) end -function AnchoredInterval{T, P}(i::T, x::Bool, y::Bool) where {T, P} - return AnchoredInterval{T, P}(i, Inclusivity(x, y)) +function AnchoredInterval{T, S}(anchor::T, span::S, args...) where {T, S} + E = if span < zero(S) + Ending + elseif span > zero(S) + Beginning + else + throw(ArgumentError("Must specify endpoint type when span is zero")) + end + AnchoredInterval{T, S, E}(anchor, span, args...) +end +function AnchoredInterval{T, S}(anchor::T, args...) where {T, S} + AnchoredInterval{T, S}(anchor, oneunit(S), args...) end -AnchoredInterval(i::T, span, inc::Inclusivity) where T = AnchoredInterval{T, span}(i, inc) -AnchoredInterval(i::T, span, x::Bool, y::Bool) where T = AnchoredInterval{T, span}(i, x, y) -AnchoredInterval(i::T, span) where T = AnchoredInterval{T, span}(i) +function AnchoredInterval{T}(anchor::T, span::S, args...) where {T, S} + AnchoredInterval{T, S}(anchor, span, args...) +end +function AnchoredInterval(anchor::T, span::S, args...) where {T, S} + AnchoredInterval{T, S}(anchor, span, args...) +end -const HourEnding{T} = AnchoredInterval{T, Hour(-1)} where T <: TimeType + +const HourEnding{T} = AnchoredInterval{T, Hour, Ending} where T <: TimeType HourEnding(a::T, args...) where T = HourEnding{T}(a, args...) -const HourBeginning{T} = AnchoredInterval{T, Hour(1)} where T <: TimeType +const HourBeginning{T} = AnchoredInterval{T, Hour, Beginning} where T <: TimeType HourBeginning(a::T, args...) where T = HourBeginning{T}(a, args...) """ @@ -105,22 +148,22 @@ nearest hour. """ HB(a, args...) = HourBeginning(floor(a, Hour), args...) -function Base.copy(x::AnchoredInterval{T, P}) where {T, P} - return AnchoredInterval{T, P}(anchor(x), inclusivity(x)) +function Base.copy(x::AnchoredInterval{T, S, E}) where {T, S, E} + return AnchoredInterval{T, S, E}(anchor(x), inclusivity(x)) end ##### ACCESSORS ##### -function Base.first(interval::AnchoredInterval{T, P}) where {T, P} - min(interval.anchor, interval.anchor + P) +function Base.first(interval::AnchoredInterval) + min(interval.anchor, interval.anchor + interval.span) end -function Base.last(interval::AnchoredInterval{T, P}) where {T, P} - max(interval.anchor, interval.anchor + P) +function Base.last(interval::AnchoredInterval) + max(interval.anchor, interval.anchor + interval.span) end anchor(interval::AnchoredInterval) = interval.anchor -span(interval::AnchoredInterval{T, P}) where {T, P} = abs(P) +span(interval::AnchoredInterval) = abs(interval.span) ##### CONVERSION ##### @@ -132,6 +175,19 @@ function Base.convert(::Type{Interval{T}}, interval::AnchoredInterval{T}) where return Interval{T}(first(interval), last(interval), inclusivity(interval)) end +# Conversion methods which currently aren't needed but could prove useful. Commented out +# since these are untested. + +#= +function Base.convert(::Type{AnchoredInterval{Ending}}, interval::Interval{T}) where T + AnchoredInterval{T}(last(interval), -span(interval), inclusivity(interval)) +end + +function Base.convert(::Type{AnchoredInterval{Beginning}}, interval::Interval{T}) where T + AnchoredInterval{T}(first(interval), span(interval), inclusivity(interval)) +end +=# + Base.convert(::Type{T}, interval::AnchoredInterval{T}) where T = anchor(interval) # Date/DateTime attempt to convert to Int64 instead of falling back to convert(T, ...) @@ -146,17 +202,21 @@ Base.show(io::IO, ::Type{HourBeginning}) = print(io, "HourBeginning{T}") Base.show(io::IO, ::Type{HourEnding{T}}) where T <: TimeType = print(io, "HourEnding{$T}") Base.show(io::IO, ::Type{HourBeginning{T}}) where T <: TimeType = print(io, "HourBeginning{$T}") -function Base.show(io::IO, ::Type{AnchoredInterval{T, P}}) where {T, P} - print(io, "AnchoredInterval{$T, $P}") +function Base.show(io::IO, ::Type{AnchoredInterval{T, S, E}}) where {T, S, E} + d = E == Beginning ? "Beginning" : "Ending" + print(io, "AnchoredInterval{$T, $S, $d}") end -function Base.show(io::IO, interval::T) where T <: AnchoredInterval +function Base.show(io::IO, interval::AnchoredInterval) if get(io, :compact, false) print(io, interval) else - print(io, "$T(") + show(io, typeof(interval)) + print(io, "(") show(io, anchor(interval)) print(io, ", ") + show(io, interval.span) + print(io, ", ") show(io, inclusivity(interval)) print(io, ")") end @@ -173,7 +233,7 @@ end ##### ARITHMETIC ##### -Base.:+(a::T, b) where {T <: AnchoredInterval} = T(anchor(a) + b, inclusivity(a)) +Base.:+(a::T, b) where {T <: AnchoredInterval} = T(anchor(a) + b, a.span, inclusivity(a)) Base.:+(a, b::AnchoredInterval) = b + a Base.:-(a::AnchoredInterval, b) = a + -b @@ -183,15 +243,20 @@ Base.:-(a::AnchoredInterval, b::AnchoredInterval) = anchor(a) - anchor(b) Base.:-(a::T, b::AnchoredInterval{T}) where {T <: Number} = a + -b -function Base.:-(a::AnchoredInterval{T, P}) where {T <: Number, P} +function Base.:-(a::AnchoredInterval{T, S, Ending}) where {T <: Number, S} + inc = inclusivity(a) + AnchoredInterval{T, S, Beginning}(-anchor(a), -a.span, Inclusivity(last(inc), first(inc))) +end + +function Base.:-(a::AnchoredInterval{T, S, Beginning}) where {T <: Number, S} inc = inclusivity(a) - AnchoredInterval{T, -P}(-anchor(a), Inclusivity(last(inc), first(inc))) + AnchoredInterval{T, S, Ending}(-anchor(a), -a.span, Inclusivity(last(inc), first(inc))) end ##### EQUALITY ##### # Required for min/max of AnchoredInterval{LaxZonedDateTime} when the anchor is AMB or DNE -function Base.:<(a::AnchoredInterval{T, P}, b::AnchoredInterval{T, P}) where {T, P} +function Base.:<(a::AnchoredInterval{T, S, E}, b::AnchoredInterval{T, S, E}) where {T, S, E} return anchor(a) < anchor(b) end @@ -203,8 +268,8 @@ function Base.steprem(a::T, b::T, c) where {T <: AnchoredInterval} end # Infer step for two-argument StepRange{<:AnchoredInterval} -function Base.colon(start::AnchoredInterval{T, P}, stop::AnchoredInterval{T, P}) where {T,P} - return colon(start, abs(P), stop) +function Base.colon(start::AnchoredInterval{T, S}, stop::AnchoredInterval{T, S}) where {T,S} + return colon(start, oneunit(S), stop) end function Base.length(r::StepRange{<:AnchoredInterval}) @@ -213,23 +278,23 @@ end ##### SET OPERATIONS ##### -function Base.isempty(interval::AnchoredInterval{T, P}) where {T, P} - return P == zero(P) && !isclosed(interval) +function Base.isempty(interval::AnchoredInterval{T, S}) where {T, S} + return span(interval) == zero(S) && !isclosed(interval) end -function Base.intersect(a::AnchoredInterval{T, P}, b::AnchoredInterval{T, Q}) where {T,P,Q} +function Base.intersect(a::AnchoredInterval{T, S, E}, b::AnchoredInterval{T, S, E}) where {T, S, E} interval = invoke(intersect, Tuple{AbstractInterval{T}, AbstractInterval{T}}, a, b) - sp = isa(P, Period) ? canonicalize(typeof(P), span(interval)) : span(interval) - if P ≤ zero(P) + sp = S <: Period ? canonicalize(S, span(interval)) : span(interval) + if E == Ending anchor = last(interval) - new_P = -sp + sp = -sp else anchor = first(interval) - new_P = sp + sp = sp end - return AnchoredInterval{T, new_P}(anchor, inclusivity(interval)) + return AnchoredInterval{T, S, E}(anchor, sp, inclusivity(interval)) end ##### UTILITIES ##### diff --git a/src/description.jl b/src/description.jl index 17a51944..895ea96a 100644 --- a/src/description.jl +++ b/src/description.jl @@ -1,21 +1,21 @@ using Base.Dates: value, coarserperiod -function description(interval::AnchoredInterval{T, P}) where {T, P} - description(interval, P > zero(P) ? "B" : "E") +function description(interval::AnchoredInterval{T, S, E}) where {T, S, E} + description(interval, E == Beginning ? "B" : "E") end -function description(interval::AnchoredInterval{T, P}, s::String) where {T, P} +function description(interval::AnchoredInterval, s::String) return string( first(inclusivity(interval)) ? '[' : '(', - description(anchor(interval), abs(P), s), + description(anchor(interval), span(interval), s), last(inclusivity(interval)) ? ']' : ')', ) end -function description(interval::AnchoredInterval{ZonedDateTime, P}, s::String) where P +function description(interval::AnchoredInterval{ZonedDateTime}, s::String) return string( first(inclusivity(interval)) ? '[' : '(', - description(anchor(interval), abs(P), s), + description(anchor(interval), span(interval), s), anchor(interval).zone.offset, last(inclusivity(interval)) ? ']' : ')', ) diff --git a/src/endpoint.jl b/src/endpoint.jl index c6695564..075096ee 100644 --- a/src/endpoint.jl +++ b/src/endpoint.jl @@ -2,6 +2,9 @@ struct Direction{T} end const Left = Direction{:Left}() const Right = Direction{:Right}() +const Beginning = Left +const Ending = Right + struct Endpoint{T, D} endpoint::T included::Bool diff --git a/test/anchoredinterval.jl b/test/anchoredinterval.jl index b6c0a53f..ef4c44d4 100644 --- a/test/anchoredinterval.jl +++ b/test/anchoredinterval.jl @@ -1,11 +1,11 @@ -using Intervals: canonicalize +using Intervals: Beginning, Ending, canonicalize @testset "AnchoredInterval" begin dt = DateTime(2016, 8, 11, 2) @testset "constructor" begin - expected = AnchoredInterval{DateTime, Hour(-1)}(dt, Inclusivity(false, true)) - @test AnchoredInterval{DateTime, Hour(-1)}(dt) == expected + expected = AnchoredInterval{DateTime, Hour}(dt, Hour(-1), Inclusivity(false, true)) + @test AnchoredInterval{DateTime, Hour}(dt, Hour(-1)) == expected @test_throws MethodError AnchoredInterval{Hour(-1)}(dt) @test AnchoredInterval(dt, Hour(-1)) == expected @test HourEnding{DateTime}(dt) == expected @@ -13,8 +13,8 @@ using Intervals: canonicalize @test HE(dt) == expected @test HE(dt - Minute(59)) == expected - expected = AnchoredInterval{DateTime, Hour(1)}(dt, Inclusivity(true, false)) - @test AnchoredInterval{DateTime, Hour(1)}(dt) == expected + expected = AnchoredInterval{DateTime, Hour}(dt, Hour(1), Inclusivity(true, false)) + @test AnchoredInterval{DateTime, Hour}(dt, Hour(1)) == expected @test_throws MethodError AnchoredInterval{Hour(1)}(dt) @test AnchoredInterval(dt, Hour(1)) == expected @test HourBeginning(dt) == expected @@ -25,8 +25,8 @@ using Intervals: canonicalize @test HourEnding{DateTime}(dt, true, false) == HourEnding{DateTime}(dt, Inclusivity(true, false)) @test HourEnding(dt, true, false) == HourEnding(dt, Inclusivity(true, false)) - @test AnchoredInterval{DateTime, Day(1)}(dt, false, false) == - AnchoredInterval{DateTime, Day(1)}(dt, Inclusivity(false, false)) + @test AnchoredInterval{DateTime}(dt, Day(1), false, false) == + AnchoredInterval{DateTime}(dt, Day(1), Inclusivity(false, false)) @test AnchoredInterval(dt, Day(1), false, false) == AnchoredInterval(dt, Day(1), Inclusivity(false, false)) @test HE(dt, true, true) == HourEnding(dt, Inclusivity(true, true)) @@ -117,108 +117,114 @@ using Intervals: canonicalize @testset "display" begin @test sprint(show, HourEnding) == "HourEnding{T}" @test sprint(show, HourBeginning) == "HourBeginning{T}" - @test sprint(show, AnchoredInterval{T, Hour(-1)} where T) == - "Intervals.AnchoredInterval{T,-1 hour} where T" - @test sprint(show, AnchoredInterval{T, Hour(1)} where T) == - "Intervals.AnchoredInterval{T,1 hour} where T" + @test sprint(show, AnchoredInterval{T, Hour, Ending} where T) == + "Intervals.AnchoredInterval{T,$Hour,$Ending} where T" + @test sprint(show, AnchoredInterval{T, Hour, Beginning} where T) == + "Intervals.AnchoredInterval{T,$Hour,$Beginning} where T" @test sprint(show, HourEnding{DateTime}) == "HourEnding{DateTime}" @test sprint(show, HourBeginning{DateTime}) == "HourBeginning{DateTime}" - @test sprint(show, AnchoredInterval{DateTime, Hour(-1)}) == "HourEnding{DateTime}" - @test sprint(show, AnchoredInterval{DateTime, Hour(1)}) == "HourBeginning{DateTime}" - - @test sprint(show, AnchoredInterval{T, Day(-1)} where T) == - "Intervals.AnchoredInterval{T,-1 day} where T" - @test sprint(show, AnchoredInterval{T, Day(1)} where T) == - "Intervals.AnchoredInterval{T,1 day} where T" - @test sprint(show, AnchoredInterval{DateTime, Day(-1)}) == - "AnchoredInterval{DateTime, -1 day}" - @test sprint(show, AnchoredInterval{DateTime, Day(1)}) == - "AnchoredInterval{DateTime, 1 day}" + @test sprint(show, AnchoredInterval{DateTime, Hour, Ending}) == + "HourEnding{DateTime}" + @test sprint(show, AnchoredInterval{DateTime, Hour, Beginning}) == + "HourBeginning{DateTime}" + + @test sprint(show, AnchoredInterval{T, Day, Ending} where T) == + "Intervals.AnchoredInterval{T,$Day,$Ending} where T" + @test sprint(show, AnchoredInterval{T, Day, Beginning} where T) == + "Intervals.AnchoredInterval{T,$Day,$Beginning} where T" + @test sprint(show, AnchoredInterval{DateTime, Day, Ending}) == + "AnchoredInterval{DateTime, $Day, Ending}" + @test sprint(show, AnchoredInterval{DateTime, Day, Beginning}) == + "AnchoredInterval{DateTime, $Day, Beginning}" interval = HourEnding(dt) @test string(interval) == "(2016-08-11 HE02]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourEnding{DateTime}(2016-08-11T02:00:00, Inclusivity(false, true))" + "HourEnding{DateTime}(2016-08-11T02:00:00, -1 hour, Inclusivity(false, true))" interval = HourEnding(DateTime(2013, 2, 13), Inclusivity(true, false)) @test string(interval) == "[2013-02-12 HE24)" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourEnding{DateTime}(2013-02-13T00:00:00, Inclusivity(true, false))" + "HourEnding{DateTime}(2013-02-13T00:00:00, -1 hour, Inclusivity(true, false))" interval = HourEnding(dt + Minute(15) + Second(30)) @test string(interval) == "(2016-08-11 HE02:15:30]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourEnding{DateTime}(2016-08-11T02:15:30, Inclusivity(false, true))" + "HourEnding{DateTime}(2016-08-11T02:15:30, -1 hour, Inclusivity(false, true))" interval = HourEnding(dt + Millisecond(2)) @test string(interval) == "(2016-08-11 HE02:00:00.002]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourEnding{DateTime}(2016-08-11T02:00:00.002, Inclusivity(false, true))" + "HourEnding{DateTime}(2016-08-11T02:00:00.002, -1 hour, Inclusivity(false, true))" interval = HourEnding(DateTime(2013, 2, 13, 0, 1), Inclusivity(true, false)) @test string(interval) == "[2013-02-13 HE00:01:00)" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourEnding{DateTime}(2013-02-13T00:01:00, Inclusivity(true, false))" + "HourEnding{DateTime}(2013-02-13T00:01:00, -1 hour, Inclusivity(true, false))" interval = HourBeginning(dt) @test string(interval) == "[2016-08-11 HB02)" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourBeginning{DateTime}(2016-08-11T02:00:00, Inclusivity(true, false))" + "HourBeginning{DateTime}(2016-08-11T02:00:00, 1 hour, Inclusivity(true, false))" interval = HourBeginning(DateTime(2013, 2, 13), Inclusivity(false, true)) @test string(interval) == "(2013-02-13 HB00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "HourBeginning{DateTime}(2013-02-13T00:00:00, Inclusivity(false, true))" + "HourBeginning{DateTime}(2013-02-13T00:00:00, 1 hour, Inclusivity(false, true))" interval = HourEnding(ZonedDateTime(dt, tz"America/Winnipeg")) @test string(interval) == "(2016-08-11 HE02-05:00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "HourEnding{$ZonedDateTime}(2016-08-11T02:00:00-05:00, ", + "HourEnding{$ZonedDateTime}(2016-08-11T02:00:00-05:00, -1 hour, ", "Inclusivity(false, true))", ) interval = AnchoredInterval(Date(dt), Year(-1)) @test string(interval) == "(YE 2016-08-11]" @test sprint(showcompact, interval) == string(interval) - @test sprint(show, interval) == - "AnchoredInterval{Date, -1 year}(2016-08-11, Inclusivity(false, true))" + @test sprint(show, interval) == string( + "AnchoredInterval{Date, $Year, Ending}(2016-08-11, -1 year, ", + "Inclusivity(false, true))" + ) interval = AnchoredInterval(ceil(Date(dt), Year), Year(-1)) @test string(interval) == "(YE 2017-01-01]" @test sprint(showcompact, interval) == string(interval) - @test sprint(show, interval) == - "AnchoredInterval{Date, -1 year}(2017-01-01, Inclusivity(false, true))" + @test sprint(show, interval) == string( + "AnchoredInterval{Date, $Year, Ending}(2017-01-01, -1 year, ", + "Inclusivity(false, true))" + ) interval = AnchoredInterval(dt, Month(-1)) @test string(interval) == "(MoE 2016-08-11 02:00:00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -1 month}(2016-08-11T02:00:00, ", - "Inclusivity(false, true))", + "AnchoredInterval{DateTime, $Month, Ending}(2016-08-11T02:00:00, -1 month, ", + "Inclusivity(false, true))" ) interval = AnchoredInterval(ceil(dt, Month), Month(-1)) @test string(interval) == "(MoE 2016-09-01]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -1 month}(2016-09-01T00:00:00, ", - "Inclusivity(false, true))", + "AnchoredInterval{DateTime, $Month, Ending}(2016-09-01T00:00:00, -1 month, ", + "Inclusivity(false, true))" ) interval = AnchoredInterval(DateTime(dt), Day(-1)) @test string(interval) == "(DE 2016-08-11 02:00:00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -1 day}(2016-08-11T02:00:00, ", + "AnchoredInterval{DateTime, $Day, Ending}(2016-08-11T02:00:00, -1 day, ", "Inclusivity(false, true))" ) @@ -226,7 +232,7 @@ using Intervals: canonicalize @test string(interval) == "(DE 2016-08-12]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -1 day}(2016-08-12T00:00:00, ", + "AnchoredInterval{DateTime, $Day, Ending}(2016-08-12T00:00:00, -1 day, ", "Inclusivity(false, true))" ) @@ -234,14 +240,16 @@ using Intervals: canonicalize interval = AnchoredInterval(Date(dt), Day(-1)) @test string(interval) == "(DE 2016-08-11]" @test sprint(showcompact, interval) == string(interval) - @test sprint(show, interval) == - "AnchoredInterval{Date, -1 day}(2016-08-11, Inclusivity(false, true))" + @test sprint(show, interval) == string( + "AnchoredInterval{Date, $Day, Ending}(2016-08-11, -1 day, ", + "Inclusivity(false, true))" + ) interval = AnchoredInterval(dt, Minute(-5)) @test string(interval) == "(2016-08-11 5ME02:00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -5 minutes}(2016-08-11T02:00:00, ", + "AnchoredInterval{DateTime, $Minute, Ending}(2016-08-11T02:00:00, -5 minutes, ", "Inclusivity(false, true))", ) @@ -249,16 +257,16 @@ using Intervals: canonicalize @test string(interval) == "(2016-08-11 30SE02:00:00]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -30 seconds}(2016-08-11T02:00:00, ", - "Inclusivity(false, true))", + "AnchoredInterval{DateTime, $Second, Ending}(2016-08-11T02:00:00, ", + "-30 seconds, Inclusivity(false, true))", ) interval = AnchoredInterval(dt, Millisecond(-10)) @test string(interval) == "(2016-08-11 10msE02:00:00.000]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == string( - "AnchoredInterval{DateTime, -10 milliseconds}(2016-08-11T02:00:00, ", - "Inclusivity(false, true))", + "AnchoredInterval{DateTime, $Millisecond, Ending}(2016-08-11T02:00:00, ", + "-10 milliseconds, Inclusivity(false, true))", ) # Non-period AnchoredIntervals @@ -266,13 +274,13 @@ using Intervals: canonicalize @test string(interval) == "(0..10]" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "AnchoredInterval{$Int, -10}(10, Inclusivity(false, true))" + "AnchoredInterval{$Int, $Int, Ending}(10, -10, Inclusivity(false, true))" interval = AnchoredInterval('a', 25) @test string(interval) == "[a..z)" @test sprint(showcompact, interval) == string(interval) @test sprint(show, interval) == - "AnchoredInterval{Char, 25}('a', Inclusivity(true, false))" + "AnchoredInterval{Char, $Int, Beginning}('a', 25, Inclusivity(true, false))" end @testset "equality" begin @@ -427,20 +435,26 @@ using Intervals: canonicalize end @testset "isempty" begin - for p in [Year(1), Month(1), Day(1), Hour(1), Minute(1), Second(1)] - for sign in [+, -] - @test !isempty(AnchoredInterval(dt, sign(p), Inclusivity(false, false))) - @test !isempty(AnchoredInterval(dt, sign(p), Inclusivity(false, true))) - @test !isempty(AnchoredInterval(dt, sign(p), Inclusivity(true, false))) - @test !isempty(AnchoredInterval(dt, sign(p), Inclusivity(true, true))) - end + for P in [Year, Month, Day, Hour, Minute, Second], sign in [+, -] + span = sign(oneunit(P)) + + @test !isempty(AnchoredInterval(dt, span, Inclusivity(false, false))) + @test !isempty(AnchoredInterval(dt, span, Inclusivity(false, true))) + @test !isempty(AnchoredInterval(dt, span, Inclusivity(true, false))) + @test !isempty(AnchoredInterval(dt, span, Inclusivity(true, true))) end - for p in [Year(0), Month(0), Day(0), Hour(0), Minute(0), Second(0)] - @test isempty(AnchoredInterval(dt, p, Inclusivity(false, false))) - @test isempty(AnchoredInterval(dt, p, Inclusivity(false, true))) - @test isempty(AnchoredInterval(dt, p, Inclusivity(true, false))) - @test !isempty(AnchoredInterval(dt, p, Inclusivity(true, true))) + for P in [Year, Month, Day, Hour, Minute, Second] + span = zero(P) + @test_throws ArgumentError AnchoredInterval(dt, span) + + for E in [Ending, Beginning] + T = AnchoredInterval{DateTime, P, E} + @test isempty(T(dt, span, Inclusivity(false, false))) + @test isempty(T(dt, span, Inclusivity(false, true))) + @test isempty(T(dt, span, Inclusivity(true, false))) + @test !isempty(T(dt, span, Inclusivity(true, true))) + end end end @@ -474,7 +488,7 @@ using Intervals: canonicalize @test isempty(intersect(HourEnding(dt), HourEnding(dt + Hour(1)))) # Single point overlap - expected = AnchoredInterval(dt, Hour(0), Inclusivity(true, true)) + expected = HourEnding(dt, Hour(0), Inclusivity(true, true)) @test intersect( HourEnding(dt, Inclusivity(true, true)), HourEnding(dt + Hour(1), Inclusivity(true, true)), @@ -485,7 +499,7 @@ using Intervals: canonicalize @test intersect(he, AnchoredInterval(dt, Hour(-2))) == he @test intersect(AnchoredInterval(dt + Hour(1), Hour(-3)), he) == he @test intersect(HourBeginning(dt - Hour(1)), he) == - HourBeginning(dt - Hour(1), Inclusivity(false, false)) + Interval(dt - Hour(1), dt, Inclusivity(false, false)) # Identical save for inclusivity expected = HourEnding(dt, Inclusivity(false, false)) @@ -498,9 +512,8 @@ using Intervals: canonicalize HourEnding(dt, Inclusivity(true, false)), ) == expected - # This should probably be an AnchoredInterval{Hour(0)}, but it's not important - @test intersect(HourEnding(dt), HourBeginning(dt)) == - AnchoredInterval(dt, Hour(0), Inclusivity(true, true)) + # Intersection of HourEnding and HourBeginning + @test intersect(HourEnding(dt), HourBeginning(dt)) == Interval(dt, dt) # Non-period AnchoredIntervals @test intersect(AnchoredInterval(3, -2), AnchoredInterval(4, -2)) ==