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…

tf:isBefore / tf:isAfter

is before / after the second.

tf:contains / tf:isContainedBy

contains / is contained by the second.

tf:isDuring / tf:hasDuring

falls during / has the second during it.

tf:isInside / tf:hasInside

is inside / has the second inside it.

tf:meets / tf:metBy

ends exactly where the second begins / begins exactly where the second ends.

tf:overlaps / tf:overlappedBy

overlaps / is overlapped by the second.

tf:starts / tf:startedBy

shares the second’s start and ends within it / contains the second’s start and extends past its end.

tf:finishes / tf:finishedBy

shares the second’s end and begins within it / contains the second’s end and begins before it.

tf:equals

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.