Docs and articles

R-54

Technology

The Half-Open Interval Is a Small Choice with Big Consequences

Does a time range include its endpoint? Different databases answer differently. The wrong answer creates phantom rows or missing rows. We picked [start, end).

PAR2 Labs

August 29, 2026

1 min

The Half-Open Interval Is a Small Choice with Big Consequences

We've watched a team spend an afternoon debugging why a daily summary report showed slightly wrong totals at midnight. The answer turned out to be that one of their databases used closed intervals [t1, t2] and another used half-open [t1, t2). A single transaction at exactly midnight got counted twice.

01

The choice

Every bitemporal interval in Tessera is half-open: [start, end). A record's valid_time interval includes the start instant and excludes the end instant. If a customer's address is valid from 2024-01-01T00:00:00 to 2024-12-31T23:59:59.999, the next address's valid_time starts at 2025-01-01T00:00:00 with no overlap.

02

Why half-open

No accidental double-counting. Boundaries belong to exactly one interval.

Composition is trivial. Adjacent intervals tile time without gaps or overlaps.

It matches every language's range syntax. Python's range(0, 10), Rust's 0..10, SQL's generate_series — all half-open.

The boring choices are the ones that matter most.

03

The query semantics

The convention holds everywhere history is queried. A point-in-time snapshot includes a record when its interval satisfies start <= t < end. A range query matches records whose interval overlaps the half-open window [s, e). And the same rule governs transaction-time queries as valid-time ones — one convention, no exceptions to remember.

The boring choices are the ones that matter most. Pick a convention, document it, be consistent. We picked half-open and stopped thinking about it. Recommended.

04

The one place to be careful

When ingesting from a system that uses closed intervals, the boundary point of their data is the start of the next interval in our data. Most importers handle this implicitly, but if you're hand-rolling an integration, normalise on the way in. A late-night incident report is the worst time to rediscover this.

Key Takeaways

01

Mixing closed and half-open intervals across systems double-counts anything that lands exactly on a boundary.

02

Every bitemporal interval in Tessera is half-open, [start, end): the start instant is included and the end excluded.

03

Half-open intervals tile time without gaps or overlaps and match range syntax in Python, Rust and SQL.

04

When ingesting from a closed-interval system, normalise the boundary on the way in.


PAR2 Labs · Technology

Talk to us

One copy of the data, five ways to read it.