# Intervals

This package defines:

`AbstractInterval`

, along with its subtypes:`Interval{T,L,R}`

, which represents a non-iterable range between two endpoints of type`T`

with left/right bounds types respectively being`L`

and`R`

`AnchoredInterval{P,T,L,R}`

, which represents a non-iterable range defined by a single value`anchor::T`

and the value type`P`

which represents the span of the range. Left/right bounds types are specifed by`L`

and`R`

respectively`HourEnding`

, a type alias for`AnchoredInterval{Hour(-1)}`

`HourBeginning`

, a type alias for`AnchoredInterval{Hour(1)}`

`HE`

and`HB`

, pseudoconstructors for`HourEnding`

and`HourBeginning`

that round the anchor up (`HE`

) or down (`HB`

) to the nearest hour

`Bound`

, abstract type for all possible bounds type classifications:

## Sets

A single interval can be used to represent a contiguous set within a domain but cannot be used to represent a disjoint set. Due to this restriction all set-based operations that return an interval will always return a vector of intervals. These operations will combine any intervals which are overlapping or touching into a single continuous interval and never return an interval instance which itself is empty.

```
julia> union([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Closed}}:
Interval{Int64, Closed, Closed}(1, 15)
julia> intersect([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Closed}}:
Interval{Int64, Closed, Closed}(5, 10)
julia> setdiff([1..10], [5..15])
1-element Vector{Interval{Int64, Closed, Open}}:
Interval{Int64, Closed, Open}(1, 5)
julia> symdiff([1..10], [5..15])
2-element Vector{Interval{Int64}}:
Interval{Int64, Closed, Open}(1, 5)
Interval{Int64, Open, Closed}(10, 15)
julia> intersect([1..5], [10..15])
Interval[]
```

## Example Usage

### Bounds

```
julia> a = Interval{Closed, Closed}(1, 10)
Interval{Int64, Closed, Closed}(1, 10)
julia> b = Interval{Open, Open}(5, 15)
Interval{Int64, Open, Open}(5, 15)
julia> 5 in a
true
julia> 5 in b
false
julia> intersect(a, b)
Interval{Int64, Open, Closed}(5, 10)
julia> c = Interval(15, 20)
Interval{Int64, Closed, Closed}(15, 20)
julia> isempty(intersect(b, c))
true
```

### Display

```
julia> a = Interval('a', 'z')
Interval{Char, Closed, Closed}('a', 'z')
julia> string(a)
"[a .. z]"
julia> using Dates
julia> b = Interval{Closed, Open}(Date(2013), Date(2016))
Interval{Date, Closed, Open}(Date("2013-01-01"), Date("2016-01-01"))
julia> string(b)
"[2013-01-01 .. 2016-01-01)"
julia> c = HourEnding(DateTime(2016, 8, 11))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T00:00:00"))
julia> string(c)
"(2016-08-10 HE24]"
```

`HourEnding`

and `HE`

```
julia> using TimeZones, Dates
julia> unrounded = HourEnding(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
julia> he = HE(ZonedDateTime(2013, 2, 13, 0, 30, tz"America/Winnipeg"))
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 1, tz"America/Winnipeg"))
julia> he + Hour(1)
HourEnding{ZonedDateTime, Open, Closed}(ZonedDateTime(2013, 2, 13, 2, tz"America/Winnipeg"))
julia> foreach(println, he:he + Day(1))
(2013-02-13 HE01-06:00]
(2013-02-13 HE02-06:00]
(2013-02-13 HE03-06:00]
(2013-02-13 HE04-06:00]
(2013-02-13 HE05-06:00]
(2013-02-13 HE06-06:00]
(2013-02-13 HE07-06:00]
(2013-02-13 HE08-06:00]
(2013-02-13 HE09-06:00]
(2013-02-13 HE10-06:00]
(2013-02-13 HE11-06:00]
(2013-02-13 HE12-06:00]
(2013-02-13 HE13-06:00]
(2013-02-13 HE14-06:00]
(2013-02-13 HE15-06:00]
(2013-02-13 HE16-06:00]
(2013-02-13 HE17-06:00]
(2013-02-13 HE18-06:00]
(2013-02-13 HE19-06:00]
(2013-02-13 HE20-06:00]
(2013-02-13 HE21-06:00]
(2013-02-13 HE22-06:00]
(2013-02-13 HE23-06:00]
(2013-02-13 HE24-06:00]
(2013-02-14 HE01-06:00]
julia> anchor(he)
2013-02-13T01:00:00-06:00
```

### Comparisons

#### Equality

Two `AbstractInterval`

s are considered equal if they have identical left and right endpoints (taking bounds into account):

```
julia> a = Interval{Closed, Open}(DateTime(2013, 2, 13), DateTime(2013, 2, 13, 1))
Interval{DateTime, Closed, Open}(DateTime("2013-02-13T00:00:00"), DateTime("2013-02-13T01:00:00"))
julia> b = Interval{Open, Closed}(DateTime(2013, 2, 13), DateTime(2013, 2, 13, 1))
Interval{DateTime, Open, Closed}(DateTime("2013-02-13T00:00:00"), DateTime("2013-02-13T01:00:00"))
julia> c = HourEnding(DateTime(2013, 2, 13, 1))
HourEnding{DateTime, Open, Closed}(DateTime("2013-02-13T01:00:00"))
julia> a == b
false
julia> b == c
true
```

#### Less Than

When determining whether one `AbstractInterval`

is less than (or greater than) another, two sets of comparison operators are available: `<`

/`>`

and `≪`

/`≫`

.

The standard `<`

and `>`

operators (which are not explicitly defined, but are derived from `isless`

) simply compare the leftmost endpoint of the intervals, and are used for things like `sort`

, `min`

, `max`

, etc.

The `≪`

and `≫`

operators (the Unicode symbols for "much less than" and "much greater than", accessible from the REPL with `\ll`

and `\gg`

, respectively) are used in this context to mean "less/greater than and disjoint"; they will verify that there is no overlap between the intervals.

```
julia> 0..10 < 10..20
true
julia> 0..10 ≪ 10..20
false
julia> 0..10 ≪ 11..20
true
```

### Rounding

Interval rounding maintains the original span of the interval, shifting it according to whichever endpoint is specified as the one to use for rounding. The operations `floor`

, `ceil`

, and `round`

are supported, as long as the `on`

keyword is supplied to specify which endpoint should be used for rounding. Valid options are `:left`

, `:right`

, or `:anchor`

if dealing with anchored intervals.

```
julia> floor(Interval(0.0, 1.0), on=:left)
Interval{Float64, Closed, Closed}(0.0, 1.0)
julia> floor(Interval(0.5, 1.0), on=:left)
Interval{Float64, Closed, Closed}(0.0, 0.5)
julia> floor(Interval(0.5, 1.5), on=:right)
Interval{Float64, Closed, Closed}(0.0, 1.0)
```

Anchored intervals default to rounding using the anchor point.

```
julia> round(AnchoredInterval{-0.5}(1.0))
AnchoredInterval{-0.5, Float64, Open, Closed}(1.0)
julia> round(AnchoredInterval{+0.5}(0.5))
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:anchor)
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:left)
AnchoredInterval{0.5, Float64, Closed, Open}(0.0)
julia> round(AnchoredInterval{+0.5}(0.5), on=:right)
AnchoredInterval{0.5, Float64, Closed, Open}(0.5)
```

### Plotting

`AbstractInterval`

subtypes can be plotted with Plots.jl.

```
julia> using Plots
julia> start_dt = DateTime(2017,1,1,0,0,0);
julia> end_dt = DateTime(2017,1,1,10,30,0);
julia> datetimes = start_dt:Hour(1):end_dt
DateTime("2017-01-01T00:00:00"):Hour(1):DateTime("2017-01-01T10:00:00")
julia> intervals = HE.(datetimes);
julia> plot(intervals, 1:11)
```

In the plot, inclusive boundaries are marked with a vertical bar, whereas exclusive boundaries just end.

## API

`Intervals.Interval`

— Type`Interval{T, L <: Bound, R <: Bound}`

An `Interval`

represents a non-iterable range or span of values (non-interable because, unlike a `StepRange`

, no step is defined).

An `Interval`

can be closed (both `first`

and `last`

are included in the interval), open (neither `first`

nor `last`

are included), or half-open. This openness is defined by the bounds information which is stored as the type parameters `L`

and `R`

.

**Example**

```
julia> interval = Interval{Closed,Open}(0, 100)
Interval{Int64,Closed,Open}}(0, 100)
julia> 0 in interval
true
julia> 50 in interval
true
julia> 100 in interval
false
julia> intersect(Interval{Open,Open}(0, 25), Interval{Closed,Closed}(20, 50)
Interval{Int64,Closed,Open}(20, 25)
```

**Infix Constructor: ..**

A closed `Interval`

can be constructed with the `..`

infix constructor:

```
julia> Dates.today() - Dates.Week(1) .. Dates.today()
Interval{Date,Closed,Closed}(2018-01-24, 2018-01-31)
```

See also: `AnchoredInterval`

`Intervals.AnchoredInterval`

— Type`AnchoredInterval{P,T,L,R}`

`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 the value type `P`

which represents the size of the range. When `P`

is positive, the `anchor`

represents the lesser endpoint (the beginning of the range); when `P`

is negative, the `anchor`

represents the greater endpoint (the end of the range).

The interval represented by an `AnchoredInterval`

value may be closed (both endpoints are included in the interval), open (neither endpoint is included), or half-open. This openness is defined by the bounds types `L`

and `R`

, which defaults to half-open (with the lesser endpoint included for positive values of `P`

and the greater endpoint included for negative values).

**Why?**

`AnchoredIntervals`

are most useful in cases where a single value is used to stand in for a 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{Hour(-1)}`

. Similarly, `HourBeginning`

is a type alias for `AnchoredInterval{Hour(1)}`

.

**Rounding**

While the user may expect an `HourEnding`

or `HourBeginning`

value to be anchored to a specific hour, the constructor makes no guarantees that the anchor provided is rounded:

```
julia> HourEnding(DateTime(2016, 8, 11, 2, 30))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T02:30:00"))
```

The `HE`

and `HB`

pseudoconstructors round the input up or down to the nearest hour, as appropriate:

```
julia> HE(DateTime(2016, 8, 11, 2, 30))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T03:00:00"))
julia> HB(DateTime(2016, 8, 11, 2, 30))
HourBeginning{DateTime, Closed, Open}(DateTime("2016-08-11T02:00:00"))
```

**Example**

```
julia> AnchoredInterval{Hour(-1)}(DateTime(2016, 8, 11, 12))
HourEnding{DateTime, Open, Closed}(DateTime("2016-08-11T12:00:00"))
julia> AnchoredInterval{Day(1)}(DateTime(2016, 8, 11))
AnchoredInterval{Day(1), DateTime, Closed, Open}(DateTime("2016-08-11T00:00:00"))
julia> AnchoredInterval{Minute(5),Closed,Closed}(DateTime(2016, 8, 11, 12, 30))
AnchoredInterval{Minute(5), DateTime, Closed, Closed}(DateTime("2016-08-11T12:30:00"))
```

`Intervals.IntervalSet`

— Type`IntervalSet{T<:AbstractInterval}`

A set of points represented by a sequence of intervals. Set operations over interval sets return a new IntervalSet, with the fewest number of intervals possible. Unbounded intervals are not supported. The individual intervals in the set can be accessed using the iteration API or by passing the set to `Array`

.

see also: https://en.wikipedia.org/wiki/Interval*arithmetic#Interval*operators

**Examples**

```
julia> union(IntervalSet(1..5), IntervalSet(3..8))
1-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 8]
julia> intersect(IntervalSet(1..5), IntervalSet(3..8))
1-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[3 .. 5]
julia> symdiff(IntervalSet(1..5), IntervalSet(3..8))
2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}:
[1 .. 3)
(5 .. 8]
julia> union(IntervalSet([1..2, 2..5]), IntervalSet(6..7))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 5]
[6 .. 7]
julia> union(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[1 .. 10]
[12 .. 14]
julia> intersect(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, Closed, Closed}}:
[4 .. 5]
[8 .. 9]
julia> setdiff(IntervalSet([1..5, 8..10]), IntervalSet([4..9, 12..14]))
2-interval IntervalSet{Interval{Int64, L, R} where {L<:Bound, R<:Bound}}:
[1 .. 4)
(9 .. 10]
```

`Intervals.HourEnding`

— Type`HourEnding{T<:TimeType, L, R} <: AbstractInterval{T}`

A type alias for `AnchoredInterval{Hour(-1), T}`

which is used to denote a 1-hour period of time which ends at a time instant (of type `T`

).

When constructing an instance of `HourEnding{T}`

the resulting interval will right-closed (of type `HourEnding{T,Open,Closed}`

).

`Intervals.HourBeginning`

— Type`HourBeginning{T<:TimeType, L, R} <: AbstractInterval{T}`

A type alias for `AnchoredInterval{Hour(1), T}`

which is used to denote a 1-hour period of time which begins at a time instant (of type `T`

).

When constructing an instance of `HourBeginning{T}`

the resulting interval will left-closed (of type `HourBeginning{T,Closed,Open}`

).

`Intervals.HE`

— Function`HE(anchor) -> HourEnding`

`HE`

is a pseudoconstructor for `HourEnding`

that rounds the anchor provided up to the nearest hour.

`Intervals.HB`

— Function`HB(anchor) -> HourBeginning`

`HB`

is a pseudoconstructor for `HourBeginning`

that rounds the anchor provided down to the nearest hour.

`Intervals.Bound`

— Type`Bound <: Any`

Abstract type representing all possible endpoint classifications (e.g. open, closed, unbounded).

`Intervals.Bounded`

— Type`Bounded <: Bound`

Abstract type indicating that the endpoint of an interval is not unbounded (e.g. open or closed).

`Intervals.Closed`

— Type`Closed <: Bounded <: Bound`

Type indicating that the endpoint of an interval is closed (the endpoint value is *included* in the interval).

`Intervals.Open`

— Type`Open <: Bounded <: Bound`

Type indicating that the endpoint of an interval is open (the endpoint value is *not included* in the interval).

`Intervals.Unbounded`

— Type`Unbounded <: Bound`

Type indicating that the endpoint of an interval is unbounded (the endpoint value is effectively infinite).

`Base.first`

— Function`first(interval::AbstractInterval{T}) -> Union{T,Nothing}`

The value of the lower endpoint. When the lower endpoint is unbounded `nothing`

will be returned.

`Base.last`

— Function`last(interval::AbstractInterval{T}) -> Union{T,Nothing}`

The value of the upper endpoint. When the upper endpoint is unbounded `nothing`

will be returned.

`Intervals.span`

— Function`span(interval::AbstractInterval) -> Any`

The delta between the upper and lower endpoints. For bounded intervals returns a non-negative value while intervals with any unbounded endpoints will throw an `ArgumentError`

.

To avoid having to capture the exception use the pattern:

`Intervals.isbounded(interval) ? span(interval) : infinity`

Where `infinity`

is a variable representing the value you wish to use to represent an unbounded, or infinite, span.

`Intervals.isclosed`

— Function`isclosed(interval) -> Bool`

Is a closed-interval: includes both of its endpoints.

`Base.isopen`

— Function`isopen(interval) -> Bool`

Is an open-interval: excludes both of its endpoints.

`Intervals.isunbounded`

— Function`isunbounded(interval) -> Bool`

Is an unbounded-interval: unbounded at both ends.

`Intervals.isbounded`

— Function`isbounded(interval) -> Bool`

Is a bounded-interval: either open, closed, left-closed/right-open, or left-open/right-closed.

Note using `!isbounded`

is commonly used to determine if any end of the interval is unbounded.

`Base.parse`

— Method`parse(::Type{Interval{T}}, str; element_parser=parse) -> Interval{T}`

Parse a string of the form `<left-type><left-value><delim><right-value><right-type>`

(e.g. `[1 .. 2)`

) as an `Interval{T}`

. The format above is interpreted as:

`left-type`

: Must be either "[" or "(" which indicates if the left-endpoint of the interval is either`Closed`

or`Open`

.`left-value`

: Specifies the value of the left-endpoint which will be parsed as the type`T`

. If the value string has a length of zero then the left-endpoint will be specified as`Unbounded`

. If the value string contains the delimiter (see below) then you may double-quote the value string to avoid any ambiguity.`delim`

: Must be either ".." or "," which indicates the delimiter separating the left/right endpoint values.`right-value`

: Specifies the value of the right-endpoint. See`left-value`

for more details.`right-type`

: Must be either "]" or ")" which indicates if the right-endpoint of the interval is either`Closed`

or`Open`

.

The `element_parser`

keyword allows a custom parser to be used when parsing the left/right values. The function is expected to take two arguments: `Type{T}`

and `AbstractString`

. This is useful for supplying additional arguments/keywords, alternative parser functions, or for types that do not define `parse`

(e.g. `String`

).

`Intervals.:≪`

— Function```
≪(a::AbstractInterval, b::AbstractInterval) -> Bool
less_than_disjoint(a::AbstractInterval, b::AbstractInterval) -> Bool
```

Less-than-and-disjoint comparison operator. Returns `true`

if `a`

is less than `b`

and they are disjoint (they do not overlap).

```
julia> 0..10 ≪ 10..20
false
julia> 0..10 ≪ 11..20
true
```

`Intervals.:≫`

— Function```
≫(a::AbstractInterval, b::AbstractInterval) -> Bool
greater_than_disjoint(a::AbstractInterval, b::AbstractInterval) -> Bool
```

Greater-than-and-disjoint comparison operator. Returns `true`

if `a`

is greater than `b`

and they are disjoint (they do not overlap).

```
julia> 10..20 ≫ 0..10
false
julia> 11..20 ≫ 0..10
true
```

`Base.:==`

— Function`==(a::Endpoint, b::Endpoint) -> Bool`

Determine if two endpoints are equal. When both endpoints are left or right then the points and inclusiveness must be the same.

Checking the equality of left-endpoint and a right-endpoint is slightly more difficult. A left-endpoint and a right-endpoint are only equal when they use the same point and are both included. Note that left/right endpoints which are both not included are not equal as the left-endpoint contains values below that point while the right-endpoint only contains values that are above that point.

Visualizing two contiguous intervals can assist in understanding this logic:

```
[x..y][y..z] -> RightEndpoint == LeftEndpoint
[x..y)[y..z] -> RightEndpoint != LeftEndpoint
[x..y](y..z] -> RightEndpoint != LeftEndpoint
[x..y)(y..z] -> RightEndpoint != LeftEndpoint
```

`Base.union`

— Function`union(intervals::IntervalSets)`

Flattens any overlapping intervals within the `IntervalSet`

into a new, smaller set containing only non-overlapping intervals.

`Base.union!`

— Function`union!(intervals::IntervalSet)`

Flattens a vector of overlapping intervals in-place to be a smaller vector containing only non-overlapping intervals.

`Intervals.superset`

— Function`superset(intervals::IntervalSet) -> Interval`

Create the smallest single interval which encompasses all of the provided intervals.

`Intervals.find_intersections`

— Function```
find_intersections(
x::AbstractVector{<:AbstractInterval},
y::AbstractVector{<:AbstractInterval}
)
```

Returns a `Vector{Vector{Int}}`

where the value at index `i`

gives the indices to all intervals in `y`

that intersect with `x[i]`

.