# 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:

## Example Usage

### Intersection

```
julia> a = 1..10
Interval{Int64, Closed, Closed}(1, 10)
julia> b = 5..15
Interval{Int64, Closed, Closed}(5, 15)
julia> intersect(a, b)
Interval{Int64, Closed, Closed}(5, 10)
```

### 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
```

### 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.

### 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)
```

## 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)
```

**Note on Ordering**

The `Interval`

constructor will compare `first`

and `last`

; if it finds that `first > last`

, they will be reversed to ensure that `first < last`

. This simplifies calls to `in`

and `intersect`

:

```
julia> i = Interval{Open,Closed}(Date(2016, 8, 11), Date(2013, 2, 13))
Interval{Date,Closed,Open}(2013-02-13, 2016-08-11)
```

Note that the bounds are also reversed in this case.

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.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::AbstractVector{<:AbstractInterval})`

Flattens a vector of overlapping intervals into a new, smaller vector containing only non-overlapping intervals.

`Base.union!`

— Function`union!(intervals::AbstractVector{<:Union{Interval, AbstractInterval}})`

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

`Intervals.superset`

— Function`superset(intervals::AbstractArray{<:AbstractInterval}) -> Interval`

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