Skip to content

Intervals

Interval is a small dataclass with union / intersection / containment / shift / scale operators. It works on any comparable type (int, float, date, datetime).

TimeWindow inherits from Interval — everything here applies to both.

Constructing

from vayu import Interval

a = Interval(1, 5)
b = Interval(3, 8)

Both endpoints must satisfy start <= end, otherwise construction raises AssertionError. Single-point intervals (Interval(3, 3)) are allowed — this lets touching intersections like [1, 3] & [3, 5] produce [3, 3] instead of crashing.

Intersection & union

a.intersects(b)        # True
a.intersection(b)      # Interval(3, 5)
a & b                  # Interval(3, 5)   (operator form)

Interval.union([a, b]) # Interval(1, 8)
a | b                  # Interval(1, 8)

Union requires overlap or contact by default — a gap raises ValueError. Pass allow_gaps=True to Interval.union([...]) to get the bounding envelope even when there's a gap:

c = Interval(10, 15)
Interval.union([a, c])                  # raises
Interval.union([a, c], allow_gaps=True) # Interval(1, 15)

Containment

3 in a                    # True
Interval(2, 4) in a       # True  (fully contained)
Interval(2, 6) in a       # False (b.end > a.end)

Shift & scale

Because Interval is generic over comparable types, arithmetic is delegated to the underlying type:

from datetime import timedelta
from vayu.time_utils import time_now

from vayu import TimeWindow
w = TimeWindow.behind(hours=1)
w + timedelta(minutes=30)    # shift forward
w - timedelta(minutes=30)    # shift back
Interval(1, 5) * 2           # Interval(2, 10)

range is the span:

Interval(1, 5).range            # 4
TimeWindow.behind(hours=1).range  # timedelta(hours=1)

Subclassing

Because operators use self.__class__, subclasses flow through naturally — two TimeWindows intersected return a TimeWindow, not a plain Interval:

from vayu import TimeWindow
(TimeWindow.behind(hours=2) & TimeWindow.ahead(hours=1)).__class__  # TimeWindow

See also