Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Implement rounding for Intervals #65

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions src/anchoredinterval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,20 @@ const HourBeginning{T} = AnchoredInterval{Hour(1), T} where T <: TimeType
HourBeginning(a::T, args...) where T = HourBeginning{T}(a, args...)

"""
HE(anchor, args...) -> HourEnding
HE(args...) -> HourEnding

`HE` is a pseudoconstructor for [`HourEnding`](@ref) that rounds the anchor provided up to the
nearest hour.
"""
HE(a, args...) = HourEnding(ceil(a, Hour), args...)
HE(args...) = ceil(HourEnding(args...), Hour)

"""
HB(anchor, args...) -> HourBeginning
HB(args...) -> HourBeginning

`HB` is a pseudoconstructor for [`HourBeginning`](@ref) that rounds the anchor provided down to the
nearest hour.
"""
HB(a, args...) = HourBeginning(floor(a, Hour), args...)
HB(args...) = floor(HourBeginning(args...), Hour)

function Base.copy(x::AnchoredInterval{P, T}) where {P, T}
return AnchoredInterval{P, T}(anchor(x), inclusivity(x))
Expand Down Expand Up @@ -274,6 +274,36 @@ function Base.intersect(a::AnchoredInterval{P, T}, b::AnchoredInterval{Q, T}) wh
return AnchoredInterval{new_P, T}(anchor, inclusivity(interval))
end

##### ROUNDING #####

for f in (:floor, :ceil, :round)
@eval function Base.$f(
interval::AnchoredInterval{P, T},
args...;
on::Type{<:Endpoint}=AnchorEndpoint,
) where {P, T}
anc = if on === AnchorEndpoint
$f(anchor(interval), args...)
elseif on === LeftEndpoint
if P ≤ zero(P)
$f(first(interval), args...) - P
else
$f(first(interval), args...)
end
elseif on === RightEndpoint
if P ≤ zero(P)
$f(last(interval), args...)
else
$f(last(interval), args...) - P
end
else
throw(ArgumentError("Unhandled `on` type: $on"))
end

return AnchoredInterval{P, T}(anc, inclusivity(interval))
end
end

##### UTILITIES #####

function canonicalize(target_type::Type{<:Period}, p::P) where P <: Period
Expand Down
17 changes: 17 additions & 0 deletions src/endpoint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ struct Endpoint{T, D}
end
end

# Unconstructable Endpoint types used for rounding
const AnchorEndpoint = Endpoint{Union{}, Direction{:Anchor}()}
const LeftAndRightEndpoint = Endpoint{Union{}, Direction{:LeftAndRight}()}

const LeftEndpoint{T} = Endpoint{T, Left}
const RightEndpoint{T} = Endpoint{T, Right}

Expand Down Expand Up @@ -106,3 +110,16 @@ Base.isless(a, b::LeftEndpoint) = a < b.endpoint || (a == b.endpoint && !b.incl
Base.isless(a, b::RightEndpoint) = a < b.endpoint
Base.isless(a::LeftEndpoint, b) = a.endpoint < b
Base.isless(a::RightEndpoint, b) = a.endpoint < b || (a.endpoint == b && !a.included)


for f in (:floor, :ceil, :round)
@eval begin
function Base.$f(p::Endpoint{T, D}) where {T, D}
Endpoint{T, D}($f(p.endpoint), p.included)
end

function Base.$f(p::Endpoint{T, D}, duration) where {T <: TimeType, D}
Endpoint{T, D}($f(p.endpoint, duration), p.included)
end
end
end
26 changes: 26 additions & 0 deletions src/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,32 @@ function Base.merge(a::AbstractInterval, b::AbstractInterval)
return Interval(left, right)
end

##### ROUNDING #####

for f in (:floor, :ceil, :round)
@eval function Base.$f(
interval::Interval,
args...;
on::Type{<:Endpoint}=LeftAndRightEndpoint,
)
left = LeftEndpoint(interval)
right = RightEndpoint(interval)

if on === LeftAndRightEndpoint
left = $f(left, args...)
right = $f(right, args...)
elseif on === LeftEndpoint
left = $f(left, args...)
elseif on === RightEndpoint
right = $f(right, args...)
else
throw(ArgumentError("Unhandled `on` type: $on"))
end

return Interval(left, right)
end
end

##### TIME ZONES #####

function astimezone(i::Interval{ZonedDateTime}, tz::TimeZone)
Expand Down
84 changes: 84 additions & 0 deletions test/anchoredinterval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -602,4 +602,88 @@ using Intervals: canonicalize
mask = a .== a[2]
@test mask == [false, true]
end

@testset "floor" begin
@test floor(AnchoredInterval{-0.5}(1.0)) == AnchoredInterval{-0.5}(1.0)
@test floor(AnchoredInterval{+0.5}(1.0)) == AnchoredInterval{+0.5}(1.0)
@test floor(AnchoredInterval{-0.5}(0.5)) == AnchoredInterval{-0.5}(0.0)
@test floor(AnchoredInterval{+0.5}(0.5)) == AnchoredInterval{+0.5}(0.0)

@test floor(AnchoredInterval{-0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{-0.5}(0.5)
@test floor(AnchoredInterval{+0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{+0.5}(1.0)
@test floor(AnchoredInterval{-0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{-0.5}(0.5)
@test floor(AnchoredInterval{+0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{+0.5}(0.0)

@test floor(AnchoredInterval{-0.5}(1.0), on=RightEndpoint) == AnchoredInterval{-0.5}(1.0)
@test floor(AnchoredInterval{+0.5}(1.0), on=RightEndpoint) == AnchoredInterval{+0.5}(0.5)
@test floor(AnchoredInterval{-0.5}(0.5), on=RightEndpoint) == AnchoredInterval{-0.5}(0.0)
@test floor(AnchoredInterval{+0.5}(0.5), on=RightEndpoint) == AnchoredInterval{+0.5}(0.5)

# Test supplying a period to floor to
interval_ending = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 1))
@test floor(interval_ending, Day) == expected
@test floor(interval_ending, Day(1)) == expected

interval_beginning = AnchoredInterval{Day(1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(1)}(DateTime(2011, 2, 1))
@test floor(interval_beginning, Day) == expected
@test floor(interval_beginning, Day(1)) == expected
end

@testset "ceil" begin
@test ceil(AnchoredInterval{-0.5}(1.0)) == AnchoredInterval{-0.5}(1.0)
@test ceil(AnchoredInterval{+0.5}(1.0)) == AnchoredInterval{+0.5}(1.0)
@test ceil(AnchoredInterval{-0.5}(0.5)) == AnchoredInterval{-0.5}(1.0)
@test ceil(AnchoredInterval{+0.5}(0.5)) == AnchoredInterval{+0.5}(1.0)

@test ceil(AnchoredInterval{-0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{-0.5}(1.5)
@test ceil(AnchoredInterval{+0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{+0.5}(1.0)
@test ceil(AnchoredInterval{-0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{-0.5}(0.5)
@test ceil(AnchoredInterval{+0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{+0.5}(1.0)

@test ceil(AnchoredInterval{-0.5}(1.0), on=RightEndpoint) == AnchoredInterval{-0.5}(1.0)
@test ceil(AnchoredInterval{+0.5}(1.0), on=RightEndpoint) == AnchoredInterval{+0.5}(1.5)
@test ceil(AnchoredInterval{-0.5}(0.5), on=RightEndpoint) == AnchoredInterval{-0.5}(1.0)
@test ceil(AnchoredInterval{+0.5}(0.5), on=RightEndpoint) == AnchoredInterval{+0.5}(0.5)

# Test supplying a period to ceil to
interval_ending = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 2))
@test ceil(interval_ending, Day) == expected
@test ceil(interval_ending, Day(1)) == expected

interval_beginning = AnchoredInterval{Day(1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(1)}(DateTime(2011, 2, 2))
@test ceil(interval_beginning, Day) == expected
@test ceil(interval_beginning, Day(1)) == expected
end

@testset "round" begin
@test round(AnchoredInterval{-0.5}(1.0)) == AnchoredInterval{-0.5}(1.0)
@test round(AnchoredInterval{+0.5}(1.0)) == AnchoredInterval{+0.5}(1.0)
@test round(AnchoredInterval{-0.5}(0.5)) == AnchoredInterval{-0.5}(0.0)
@test round(AnchoredInterval{+0.5}(0.5)) == AnchoredInterval{+0.5}(0.0)

@test round(AnchoredInterval{-0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{-0.5}(0.5)
@test round(AnchoredInterval{+0.5}(1.0), on=LeftEndpoint) == AnchoredInterval{+0.5}(1.0)
@test round(AnchoredInterval{-0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{-0.5}(0.5)
@test round(AnchoredInterval{+0.5}(0.5), on=LeftEndpoint) == AnchoredInterval{+0.5}(0.0)

@test round(AnchoredInterval{-0.5}(1.0), on=RightEndpoint) == AnchoredInterval{-0.5}(1.0)
@test round(AnchoredInterval{+0.5}(1.0), on=RightEndpoint) == AnchoredInterval{+0.5}(1.5)
@test round(AnchoredInterval{-0.5}(0.5), on=RightEndpoint) == AnchoredInterval{-0.5}(0.0)
@test round(AnchoredInterval{+0.5}(0.5), on=RightEndpoint) == AnchoredInterval{+0.5}(0.5)

# Test supplying a period to round to
interval_ending = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(-1)}(DateTime(2011, 2, 2))
@test round(interval_ending, Day) == expected
@test round(interval_ending, Day(1)) == expected

interval_beginning = AnchoredInterval{Day(1)}(DateTime(2011, 2, 1, 12))
expected = AnchoredInterval{Day(1)}(DateTime(2011, 2, 2))
@test round(interval_beginning, Day) == expected
@test round(interval_beginning, Day(1)) == expected
end
end
69 changes: 69 additions & 0 deletions test/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -556,4 +556,73 @@
]
@test union(intervals) == [Interval(-100, -1, Inclusivity(false, true))]
end

@testset "floor" begin
@test floor(Interval(0.0, 1.0)) == Interval(0.0, 1.0)
@test floor(Interval(0.5, 1.0)) == Interval(0.0, 1.0)
@test floor(Interval(0.0, 1.5)) == Interval(0.0, 1.0)
@test floor(Interval(0.5, 1.5)) == Interval(0.0, 1.0)

@test floor(Interval(0.0, 1.0), on=LeftEndpoint) == Interval(0.0, 1.0)
@test floor(Interval(0.5, 1.0), on=LeftEndpoint) == Interval(0.0, 1.0)
@test floor(Interval(0.0, 1.5), on=LeftEndpoint) == Interval(0.0, 1.5)
@test floor(Interval(0.5, 1.5), on=LeftEndpoint) == Interval(0.0, 1.5)

@test floor(Interval(0.0, 1.0), on=RightEndpoint) == Interval(0.0, 1.0)
@test floor(Interval(0.5, 1.0), on=RightEndpoint) == Interval(0.5, 1.0)
@test floor(Interval(0.0, 1.5), on=RightEndpoint) == Interval(0.0, 1.0)
@test floor(Interval(0.5, 1.5), on=RightEndpoint) == Interval(0.5, 1.0)

# Test supplying a period to floor to
interval = Interval(DateTime(2011, 2, 1, 6), DateTime(2011, 2, 2, 18))
expected = Interval(DateTime(2011, 2, 1), DateTime(2011, 2, 2))
@test floor(interval, Day) == expected
@test floor(interval, Day(1)) == expected
end

@testset "ceil" begin
@test ceil(Interval(0.0, 1.0)) == Interval(0.0, 1.0)
@test ceil(Interval(0.5, 1.0)) == Interval(1.0, 1.0)
@test ceil(Interval(0.0, 1.5)) == Interval(0.0, 2.0)
@test ceil(Interval(0.5, 1.5)) == Interval(1.0, 2.0)

@test ceil(Interval(0.0, 1.0), on=LeftEndpoint) == Interval(0.0, 1.0)
@test ceil(Interval(0.5, 1.0), on=LeftEndpoint) == Interval(1.0, 1.0)
@test ceil(Interval(0.0, 1.5), on=LeftEndpoint) == Interval(0.0, 1.5)
@test ceil(Interval(0.5, 1.5), on=LeftEndpoint) == Interval(1.0, 1.5)

@test ceil(Interval(0.0, 1.0), on=RightEndpoint) == Interval(0.0, 1.0)
@test ceil(Interval(0.5, 1.0), on=RightEndpoint) == Interval(0.5, 1.0)
@test ceil(Interval(0.0, 1.5), on=RightEndpoint) == Interval(0.0, 2.0)
@test ceil(Interval(0.5, 1.5), on=RightEndpoint) == Interval(0.5, 2.0)

# Test supplying a period to ceil to
interval = Interval(DateTime(2011, 2, 1, 6), DateTime(2011, 2, 2, 18))
expected = Interval(DateTime(2011, 2, 2), DateTime(2011, 2, 3))
@test ceil(interval, Day) == expected
@test ceil(interval, Day(1)) == expected
end

@testset "round" begin
@test round(Interval(0.0, 1.0)) == Interval(0.0, 1.0)
@test round(Interval(0.5, 1.0)) == Interval(0.0, 1.0)
@test round(Interval(0.0, 1.5)) == Interval(0.0, 2.0)
@test round(Interval(0.5, 1.5)) == Interval(0.0, 2.0)

@test round(Interval(0.0, 1.0), on=LeftEndpoint) == Interval(0.0, 1.0)
@test round(Interval(0.5, 1.0), on=LeftEndpoint) == Interval(0.0, 1.0)
@test round(Interval(0.0, 1.5), on=LeftEndpoint) == Interval(0.0, 1.5)
@test round(Interval(0.5, 1.5), on=LeftEndpoint) == Interval(0.0, 1.5)

@test round(Interval(0.0, 1.0), on=RightEndpoint) == Interval(0.0, 1.0)
@test round(Interval(0.5, 1.0), on=RightEndpoint) == Interval(0.5, 1.0)
@test round(Interval(0.0, 1.5), on=RightEndpoint) == Interval(0.0, 2.0)
@test round(Interval(0.5, 1.5), on=RightEndpoint) == Interval(0.5, 2.0)

# Test supplying a period to round to
interval = Interval(DateTime(2011, 2, 1, 6), DateTime(2011, 2, 2, 18))
expected = Interval(DateTime(2011, 2, 1), DateTime(2011, 2, 3))
@test round(interval, Day) == expected
@test round(interval, Day(1)) == expected
end
end