Between is a library for working with (time) intervals and the relations between them. It takes as a basis the thirteen relations of Allen's Interval Algebra. This is a system for reasoning about (temporal) intervals as described in the paper Maintaining Knowledge about Temporal Intervals.
Between is published for Scala 2.13 and Scala 3. To start using it add the following to your build.sbt:
libraryDependencies += "nl.gn0s1s" %% "between" % "0.6.0"
When the endpoints of an interval are known, the Interval[T] case class is available for testing all possible
relations between intervals. It needs two values - and + of type T which reflect the (inclusive)
endpoints of an interval. For the type T there needs to be an implicit Ordering trait available. Additionally the
endpoint - needs to be smaller than the endpoint +.
The example below shows two examples for Double and java.time.Instant:
import nl.gn0s1s.between._
val i = Interval[Int](1, 2) // i: nl.gn0s1s.between.Interval[Int] = Interval(1,2)
val j = Interval[Int](2, 3) // j: nl.gn0s1s.between.Interval[Int] = Interval(2,3)
i meets j // res0: Boolean = true
j metBy i // res1: Boolean = true
val k = Interval[java.time.Instant](java.time.Instant.ofEpochSecond(1000L), java.time.Instant.ofEpochSecond(2000L))
// k: nl.gn0s1s.between.Interval[java.time.Instant] = Interval(1970-01-01T00:16:40Z,1970-01-01T00:33:20Z)
val l = Interval[java.time.Instant](java.time.Instant.ofEpochSecond(1500L), java.time.Instant.ofEpochSecond(2500L))
// l: nl.gn0s1s.between.Interval[java.time.Instant] = Interval(1970-01-01T00:25:00Z,1970-01-01T00:41:40Z)
k overlaps l // res2: Boolean = true
l overlappedBy k // res3: Boolean = trueGiven two intervals there is always only one of the following thirteen defined relations true:
| relation | symbol | inverse | inverse relation | diagram |
|---|---|---|---|---|
x before y |
< |
> |
y after x |
xxx yyy |
x equals y |
== |
== |
y equals x |
xxx |
x meets y |
m |
mi |
y metBy x |
xxxyyy |
x overlaps y |
o |
oi |
y overlappedBy x |
xxx |
x during y |
d |
di |
y contains x |
xxx |
x starts y |
s |
si |
y startedBy x |
xxx |
x finishes y |
f |
fi |
y finishedBy x |
xxx |
beforeandafterare also available asprecedesandprecededBy, respectively.finishesandfinishedByare also available asendsandendedBy.
There's a findRelation method which can be used to find out which relation exists between two intervals. The
Relation has an inverse method implemented, which gives the inverse of a relation.
import nl.gn0s1s.between._
val i = Interval[Int](1, 2) // i: nl.gn0s1s.between.Interval[Int] = Interval(1,2)
val j = Interval[Int](2, 3) // j: nl.gn0s1s.between.Interval[Int] = Interval(2,3)
val relationBetweenIAndJ = i.findRelation(j) // relationBetweenIAndJ: nl.gn0s1s.between.Relation = m
relationBetweenIAndJ.inverse // res0: nl.gn0s1s.between.Relation = miA number of additional methods are availabe on the Interval[T] case class, some of which may be familiar for users of
the ThreeTen-Extra Interval class.
abuts, checks if the interval abuts the supplied intervalencloses, checks if the interval encloses the supplied intervalenclosedBy, checks if the interval is enclosed by the supplied intervalgap, returns the interval that is between this interval and the supplied intervalintersection, returns the intersection of this interval and the supplied intervalminus, returns the result of subtracting the supplied interval from this intervalspan, returns the smallest interval that contains this interval and the supplied intervalunion, returns the union of this interval and the supplied interval
Some point related methods are:
after, checks if the interval is after the supplied pointbefore, checks if the interval is before the supplied pointchop, chops this interval into two intervals that meet at the supplied pointclamp, clamps a supplied point within the intervalcontains, checks if supplied point is within the intervalendsAt, checks if the interval ends at the supplied pointstartsAt, checks if the interval starts at the supplied pointwith-, returns a copy of this interval with the supplied-endpointwith+, returns a copy of this interval with the supplied+endpoint
I got inspired to write this library during Eric Evans' talk at the
Domain-Driven Design Europe 2018 conference. I started writing it in the train on my way
back from the conference, this can be represented like this:
write lib <-(o)- - train - -(>, mi)-> DDD Europe - -(di)-> EE talk <-(d) - - inspired
Since the composition table of relations and the constraints method are implemented we can find out what the possible
relations between write lib and DDD Europe are:
import nl.gn0s1s.between._
Relation.constraints(Set(o), Set(<, m)) // res0: Set[nl.gn0s1s.between.Relation] = Set(<)Allen's Interval Algebra:
- Maintaining Knowledge about Temporal Intervals
- Wikipedia entry
- Thomas A. Alspaugh's Foundations Material on Allen's Interval Algebra
- Moments and Points in an Interval-Based Temporal Logic
Related links:
- A Modal Logic for Chopping Intervals
- SOWL QL: Querying Spatio - Temporal Ontologies in OWL
- AsterixDB Temporal Functions: Allen’s Relations
- Haskell package that does something similar for Haskell - https://github.com/novisci/interval-algebra
The code is available under the Mozilla Public License, version 2.0.