Temporal Data
Time-typed values in Waratah are first-class: they use the standard XSD
datatypes — xsd:dateTime, xsd:date, xsd:gYear, xsd:gYearMonth —
and both loading and querying treat them specially. There is nothing to
enable; write temporal literals into your data and the support below
applies.
This page’s examples use an event dataset shaped like this:
@prefix ex: <http://ex/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:flood ex:label "Flood" ;
ex:observedAt "2025-03-15T12:00:00Z"^^xsd:dateTime .
ex:drought ex:label "Drought" ;
ex:observedAt "2023-08-01"^^xsd:date .
ex:survey ex:label "Survey" ;
ex:observedAt "2026"^^xsd:gYear .
Range queries: time windows
The everyday temporal question is a window — everything at or after one time and at or before another:
PREFIX ex: <http://ex/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?event WHERE {
?event ex:observedAt ?t .
FILTER(?t >= "2023-01-01"^^xsd:date && ?t <= "2026-12-31"^^xsd:date)
}
The planner recognizes comparison filters over temporal-typed values and
answers them from the temporal index — a compact hierarchical posting
structure built as data loads — instead of scanning and testing every
row. Candidates read from the index are re-checked with the exact
comparison semantics, so results match a plain FILTER precisely. That
exactness includes the XSD timezone rules: the same instant written with
different UTC offsets compares equal, and a time without a timezone
that is too close to the bound to decide (within the 14-hour offset
range) is excluded — the same outcome an unindexed filter produces.
The index looks after itself
Postings are derived at load time. If the index is missing or stale — for instance after loading data with an offline tool — the compute node rebuilds it automatically at startup, before serving queries. The manual command does the same thing on demand, and is required after changing the temporal covering level parameters:
waratah temporal rebuild-index --data-dir ./waratah-data
Time relations: the tf: functions
Beyond comparisons, the tf: function family (the timefuncs vocabulary,
namespace https://w3id.org/timefuncs/) tests relationships between two
time values. Each function takes two arguments and returns true or
false:
| Pair | True when the first value… |
|---|---|
|
is before / after the second. |
|
contains / is contained by the second. |
|
falls during / has the second during it. |
|
is inside / has the second inside it. |
|
ends exactly where the second begins / begins exactly where the second ends. |
|
overlaps / is overlapped by the second. |
|
shares the second’s start and ends within it / contains the second’s start and extends past its end. |
|
shares the second’s end and begins within it / contains the second’s end and begins before it. |
|
is the same time as the second. |
Declare the namespace and use them in FILTER like any boolean function:
PREFIX tf: <https://w3id.org/timefuncs/>
SELECT ?event WHERE {
?event ex:observedAt ?t .
FILTER(tf:isBefore(?t, "2026-01-01"^^xsd:date))
}
One spec deviation to know
Ordering comparisons across the xsd:gYear family (comparing a year
with a year-month, for example) are a deliberate, documented extension
of the spec’s comparison rules. Run with --strict-sparql if you need
spec-strict behavior — those comparisons become type errors — and see
Deviations from Spec for the full
list.