---
tags:
  - woql
  - reference
  - time-processing
  - advanced
title: Allen's Interval Algebra and ISO 8601 Intervals
nextjs:
  metadata:
    title: Allen's Interval Algebra and ISO 8601 Intervals
    keywords: allen interval algebra iso8601 half-open inclusive duration dateRange dateTimeInterval temporal reasoning woql
    description: Guide to Allen's Interval Algebra in WOQL — covering the xdd:dateTimeInterval type, typecasting between inclusive and half-open intervals, all 13 Allen relations, interval classification, and converting between reporting durations and temporal intervals.
    openGraph:
      images: https://assets.terminusdb.com/docs/technical-documentation-terminuscms-og.png
    alternates:
      canonical: https://terminusdb.com/docs/woql-interval-algebra/
media: []
---

Every calendar has two stories to tell. One is the human story: "Q1 runs from January 1 to March 31." The other is the computational story: "Q1 is the half-open interval [January 1, April 1)" (notice the end bracket for the half-open interval). These two stories describe the same period, but they use different conventions — and mixing them up causes real bugs in financial systems, scheduling engines, and regulatory reporting.

This page explains the difference, introduces TerminusDB's `xdd:dateTimeInterval` type for ISO 8601 intervals, and shows how to use Allen's Interval Algebra to reason about temporal relationships in WOQL. Unfortunately the XSD Data Types do not include an explicit interval type, so we include it in the xdd namespace instead.

> **See also:** [WOQL Time Handling](/docs/woql-time-handling/) | [Data Types Reference](/docs/xsd-data-types/) | [WOQL Class Reference](/docs/woql-class-reference-guide/) | [dateTimeInterval semantics](/docs/datetime-interval-semantics/)

---

## The Gap in XSD: No Interval Type

The W3C XML Schema Definition (XSD) provides types for individual time points (`xsd:date`, `xsd:dateTime`) and for durations (`xsd:duration`), but it does not define a type for **time intervals** — periods anchored to specific dates.

ISO 8601, the international standard for date and time representation, does define time intervals in four forms:

| Form | Example | Description |
|------|---------|-------------|
| Start/end | `2025-01-01/2025-03-31` | Two explicit endpoints; date-only end is stored as the next day's midnight, but when submitted as `xsd:date` only it is treated as the inclusive end date (including the full end day duration) |
| Start/duration | `2025-01-01/P3M` | Start date plus a duration |
| Duration/end | `P3M/2025-04-01` | Duration ending at a date |
| Duration only | `P3M` | A length of time with no anchor |

XSD's `xsd:duration` only covers the fourth form. The first three — which are the most useful for real-world scheduling and reporting — have no standard XSD type.

TerminusDB fills this gap with `xdd:dateTimeInterval`.

---

## Inclusive vs Half-Open: The Conversion Problem

### How humans express periods

In financial reporting, regulatory filings, and everyday communication, date ranges are **inclusive** — both the start and end dates are part of the period:

> "Q1 2025 runs from **January 1** to **March 31**."

Both January 1 and March 31 are included in Q1. This is the convention used by `xdd:dateRange`, which stores inclusive ranges as `[2025-01-01, 2025-03-31]`.

### How temporal algebra works

Temporal reasoning systems — including Allen's Interval Algebra, SQL's `PERIOD` type, and most database internals — use **half-open** intervals where the start is included but the end is excluded:

> Q1 2025 = `[2025-01-01, 2025-04-01]` — includes January 1, excludes April 1.

Half-open intervals have three properties that make them essential for computation:

- **No gaps**: Q1 ends at April 1; Q2 starts at April 1. There is no missing day between them.
- **No overlaps**: March 31 belongs to Q1 only. April 1 belongs to Q2 only.
- **Clean partitioning**: The year `[Jan 1, Jan 1 next year)  ` divides exactly into 12 monthly intervals with no gaps or overlaps.

### The one-day offset

Converting between inclusive and half-open is straightforward — add one day to the inclusive end:

```
Inclusive:   [2025-01-01, 2025-03-31]     ← xdd:dateRange
Half-open:   [2025-01-01, 2025-04-01)     ← xdd:dateTimeInterval
                                  ↑
                        March 31 + 1 day = April 1
```

And to convert back, subtract one day from the exclusive end:

```
Half-open:   [2025-01-01, 2025-04-01)     ← xdd:dateTimeInterval
Inclusive:   [2025-01-01, 2025-03-31]     ← xdd:dateRange
                                  ↑
                        April 1 - 1 day = March 31
```

TerminusDB handles this conversion automatically through typecasting. Use `interval` to construct half-open intervals from inclusive dates; use `typecast` to `xdd:dateRange` to convert back for display.

---

## The Three Temporal Interval Types

TerminusDB provides three types for temporal extent, each serving a different purpose:

{% table %}
* Type
* Notation
* Semantics
* Use case
---
* **`xsd:duration`**
* `P3M`, `P1Y2M10DT2H30M`
* A length of time — no anchor to specific dates
* Offsets, deadlines, durations between events
---
* **`xdd:dateRange`**
* `[2025-01-01, 2025-03-31]`
* Inclusive/closed — both endpoints included
* Human-readable reporting periods, regulatory filings
---
* **`xdd:dateTimeInterval`**
* `2025-01-01/2025-03-31` (input) → stored as `2025-01-01T00:00:00Z/2025-04-01T00:00:00Z`
* Half-open — start included, end excluded. ISO 8601 interval notation; plain-date ends are stored at the next day's `00:00:00Z`.
* Temporal algebra, scheduling, partitioning, Allen's relations
{% /table %}

### Type hierarchy

`xdd:dateTimeInterval` inherits from `xsd:duration`. This means any place that accepts a duration can also accept an interval, and intervals can be typecast to durations to extract the duration component.

```
xsd:anySimpleType
  └─ xsd:duration              ← pure durations (P3M, P1Y)
       ├─ xsd:yearMonthDuration
       ├─ xsd:dayTimeDuration
       └─ xdd:dateTimeInterval ← ISO 8601 intervals (all 4 forms)
```

---

## `xdd:dateTimeInterval` — ISO 8601 Intervals

### String notation

The type uses the ISO 8601 solidus (`/`) separator. All four interval forms are supported:

```
2025-01-01/2025-03-31              ← Form 1: start/end (dates); stored as 2025-01-01T00:00:00Z/2025-04-01T00:00:00Z
2025-01-01T00:00:00Z/2025-04-01T00:00:00Z  ← Form 1: start/end (dateTimes)
2025-01-01/P3M                     ← Form 2: start/duration
P3M/2025-04-01                     ← Form 3: duration/end
P3M                                ← Form 4: duration only
```

### Extracting dates from intervals

When you unpack an interval to get its start and end dates, the result depends on which form was used:

{% table %}
* Form
* Start date
* End date
---
* **1. Start/end**
* Directly available
* Directly available
---
* **2. Start/duration**
* Directly available
* Computed from start + duration
---
* **3. Duration/end**
* Computed from end - duration
* Directly available
---
* **4. Duration only**
* `rdf:nil` (no anchor)
* `rdf:nil` (no anchor)
{% /table %}

### Using in a schema

```json
{
  "@type": "Class",
  "@id": "ReportingPeriod",
  "label": { "@type": "xsd:string" },
  "interval": { "@type": "xdd:dateTimeInterval" }
}
```

---

## Typecasting Between Types

TerminusDB's `typecast` predicate converts between temporal types. Each conversion direction is shown below.

### String ↔ dateTimeInterval

Parse an ISO 8601 interval string, or format an interval back to a string:

```javascript
// Parse string to interval
WOQL.typecast(
  literal("2025-01-01/2025-03-31", "xsd:string"),
  "xdd:dateTimeInterval",
  v.interval)
// v.interval = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval

// Format interval to string
WOQL.typecast(v.interval, "xsd:string", v.str)
```

### dateRange → dateTimeInterval (inclusive → half-open)

Converts the inclusive end date by adding one day:

```javascript
// [2025-01-01, 2025-03-31] → 2025-01-01T00:00:00Z/2025-04-01T00:00:00Z
WOQL.typecast(v.date_range, "xdd:dateTimeInterval", v.interval)
```

### dateTimeInterval → dateRange (half-open → inclusive)

Converts the exclusive end date by subtracting one day:

```javascript
// 2025-01-01T00:00:00Z/2025-04-01T00:00:00Z → [2025-01-01, 2025-03-31]
WOQL.typecast(v.interval, "xdd:dateRange", v.date_range)
```

### duration ↔ dateTimeInterval

A plain duration becomes a form-4 interval (duration only). Extracting the duration from an interval returns the duration component:

```javascript
// Wrap duration as interval
WOQL.typecast(
  literal("P3M", "xsd:duration"),
  "xdd:dateTimeInterval",
  v.interval)

// Extract duration from interval
WOQL.typecast(v.interval, "xsd:duration", v.dur)
```

---

## Constructing and Unpacking Intervals

Two WOQL predicates provide direct construction and deconstruction of `xdd:dateTimeInterval` values.

### `interval` — Half-Open Construction

Creates or unpacks an interval using half-open semantics. The end date is exclusive.

```javascript
// Construct: two dates → interval value
let v = Vars("q1");
WOQL.interval(
  literal("2025-01-01", "xsd:date"),
  literal("2025-03-31", "xsd:date"),
  v.q1)
// v.q1 = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval

// Unpack: interval value → two dateTimes
let v = Vars("start", "end");
WOQL.interval(v.start, v.end, some_interval)
// v.start = "2025-01-01T00:00:00Z"^^xsd:dateTime
// v.end   = "2025-04-01T00:00:00Z"^^xsd:dateTime
```

For a duration-only interval (form 4), both start and end unify with `rdf:nil`.

### `interval` — Inclusive Date Input, Half-Open Storage

`interval` accepts inclusive `xsd:date` endpoints. A date-only end is converted to an exclusive `xsd:dateTime` by adding one day. When deconstructing an interval, both endpoints are returned as `xsd:dateTime` values.

```javascript
// Construct from inclusive dates (the reporting convention)
let v = Vars("q1");
WOQL.interval(
  literal("2025-01-01", "xsd:date"),
  literal("2025-03-31", "xsd:date"),  // inclusive end as written
  v.q1)
// v.q1 = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval
// (March 31 + 1 day = April 1 stored as exclusive end)

// Deconstruct returns dateTime endpoints
let v = Vars("start", "end");
WOQL.interval(v.start, v.end, some_interval)
// v.start = "2025-01-01T00:00:00Z"^^xsd:dateTime
// v.end   = "2025-04-01T00:00:00Z"^^xsd:dateTime  (stored half-open end)
```

To recover inclusive dates for display, typecast the interval to `xdd:dateRange`:

```javascript
WOQL.typecast(some_interval, "xdd:dateRange", v.range)
// v.range = "[2025-01-01, 2025-03-31]"^^xdd:dateRange
```

---

## Allen's Interval Algebra — The 13 Relations

James F. Allen introduced his interval algebra in 1983 as a framework for temporal reasoning. Given any two proper intervals (where start < end), exactly one of 13 relations holds between them. This makes the algebra **exhaustive** (one always applies) and **mutually exclusive** (only one applies).

### The relations

The 13 relations form 6 pairs of inverses plus `equals`:

{% table %}
* Relation
* Inverse
* X condition (half-open)
* Description
---
* `before`
* `after`
* X.end < Y.start
* X finishes before Y starts (with a gap)
---
* `meets`
* `met_by`
* X.end = Y.start
* X finishes exactly where Y starts (no gap, no overlap)
---
* `overlaps`
* `overlapped_by`
* X.start < Y.start, X.end > Y.start, X.end < Y.end
* X starts first and partially overlaps Y
---
* `starts`
* `started_by`
* X.start = Y.start, X.end < Y.end
* X starts at the same time as Y but ends earlier
---
* `during`
* `contains`
* X.start > Y.start, X.end < Y.end
* X is entirely within Y
---
* `finishes`
* `finished_by`
* X.start > Y.start, X.end = Y.end
* X starts after Y but they end together
---
* `equals`
* (self-inverse)
* X.start = Y.start, X.end = Y.end
* Identical intervals
{% /table %}

### Timeline visualization

```
before:        X━━━━━━━┛         ┗━━━━━━━Y
meets:         X━━━━━━━┫━━━━━━━Y
overlaps:      X━━━━━━━╋━━━┛
                       ┗━━━━━━━Y
starts:        ┣━━━X━━━┛
               ┣━━━━━━━━━━━Y━━━┛
during:            ┣━━X━━┛
               ┣━━━━━━━━━━━Y━━━┛
finishes:              ┣━━━X━━━┫
               ┣━━━━━━━━━━━Y━━━┫
equals:        ┣━━━━━━━━━━━━━━━┫  (same interval)
```

---

## Checking All 13 Relations — Worked Examples

Each example uses the existing `interval_relation` predicate with four explicit endpoints (half-open). The `/2` arity using `xdd:dateTimeInterval` values will be available after the interval type is implemented.

### TC-AIR-01: `before` — Q1 ends before Q3 starts {#tc-air-01}

```javascript
// Q1 [Jan 1, Apr 1) is before Q3 [Jul 1, Oct 1) — there's a gap (Q2)
WOQL.interval_relation("before",
  literal("2025-01-01", "xsd:date"), literal("2025-04-01", "xsd:date"),
  literal("2025-07-01", "xsd:date"), literal("2025-10-01", "xsd:date"))
// Succeeds
```

### TC-AIR-02: `after` — Q3 is after Q1 {#tc-air-02}

```javascript
// Q3 [Jul 1, Oct 1) is after Q1 [Jan 1, Apr 1)
WOQL.interval_relation("after",
  literal("2025-07-01", "xsd:date"), literal("2025-10-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-04-01", "xsd:date"))
// Succeeds — "after" is the inverse of "before"
```

### TC-AIR-03: `meets` — Q1 meets Q2 {#tc-air-03}

```javascript
// Q1 [Jan 1, Apr 1) meets Q2 [Apr 1, Jul 1) — no gap, no overlap
WOQL.interval_relation("meets",
  literal("2025-01-01", "xsd:date"), literal("2025-04-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"), literal("2025-07-01", "xsd:date"))
// Succeeds — Q1's exclusive end equals Q2's start
```

### TC-AIR-04: `met_by` — Q2 is met by Q1 {#tc-air-04}

```javascript
// Q2 [Apr 1, Jul 1) is met_by Q1 [Jan 1, Apr 1)
WOQL.interval_relation("met_by",
  literal("2025-04-01", "xsd:date"), literal("2025-07-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-04-01", "xsd:date"))
// Succeeds — inverse of "meets"
```

### TC-AIR-05: `overlaps` — Overlapping project phases {#tc-air-05}

```javascript
// Design phase [Jan, Jun) overlaps with Development phase [Apr, Oct)
WOQL.interval_relation("overlaps",
  literal("2025-01-01", "xsd:date"), literal("2025-06-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"), literal("2025-10-01", "xsd:date"))
// Succeeds — Design starts first, ends during Development
```

### TC-AIR-06: `overlapped_by` — Reverse overlap {#tc-air-06}

```javascript
// Development [Apr, Oct) is overlapped_by Design [Jan, Jun)
WOQL.interval_relation("overlapped_by",
  literal("2025-04-01", "xsd:date"), literal("2025-10-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-06-01", "xsd:date"))
// Succeeds — inverse of "overlaps"
```

### TC-AIR-07: `starts` — First week starts with January {#tc-air-07}

```javascript
// First week of January [Jan 1, Jan 8) starts with January [Jan 1, Feb 1]
WOQL.interval_relation("starts",
  literal("2025-01-01", "xsd:date"), literal("2025-01-08", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-02-01", "xsd:date"))
// Succeeds — same start, week ends before month ends
```

### TC-AIR-08: `started_by` — January started by first week {#tc-air-08}

```javascript
// January [Jan 1, Feb 1] is started_by the first week [Jan 1, Jan 8)
WOQL.interval_relation("started_by",
  literal("2025-01-01", "xsd:date"), literal("2025-02-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-01-08", "xsd:date"))
// Succeeds — inverse of "starts"
```

### TC-AIR-09: `during` — Meeting during a work day {#tc-air-09}

```javascript
// A 1-hour meeting [10:00, 11:00) during a work day [09:00, 17:00)
WOQL.interval_relation("during",
  literal("2025-03-15T10:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T11:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T09:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T17:00:00Z", "xsd:dateTime"))
// Succeeds — meeting is entirely within the work day
```

### TC-AIR-10: `contains` — Work day contains meeting {#tc-air-10}

```javascript
// Work day [09:00, 17:00) contains the meeting [10:00, 11:00)
WOQL.interval_relation("contains",
  literal("2025-03-15T09:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T17:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T10:00:00Z", "xsd:dateTime"),
  literal("2025-03-15T11:00:00Z", "xsd:dateTime"))
// Succeeds — inverse of "during"
```

### TC-AIR-11: `finishes` — Last week finishes with January {#tc-air-11}

```javascript
// Last week of Jan [Jan 25, Feb 1] finishes with January [Jan 1, Feb 1]
WOQL.interval_relation("finishes",
  literal("2025-01-25", "xsd:date"), literal("2025-02-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2025-02-01", "xsd:date"))
// Succeeds — same end, week starts after month starts
```

### TC-AIR-12: `finished_by` — January finished by last week {#tc-air-12}

```javascript
// January [Jan 1, Feb 1] is finished_by last week [Jan 25, Feb 1]
WOQL.interval_relation("finished_by",
  literal("2025-01-01", "xsd:date"), literal("2025-02-01", "xsd:date"),
  literal("2025-01-25", "xsd:date"), literal("2025-02-01", "xsd:date"))
// Succeeds — inverse of "finishes"
```

### TC-AIR-13: `equals` — Same interval {#tc-air-13}

```javascript
// Two references to the same fiscal year
WOQL.interval_relation("equals",
  literal("2025-01-01", "xsd:date"), literal("2026-01-01", "xsd:date"),
  literal("2025-01-01", "xsd:date"), literal("2026-01-01", "xsd:date"))
// Succeeds — identical start and end
```

---

## Classifying an Interval Relation

When you do not know which relation holds between two intervals, leave the relation as an unbound variable. TerminusDB will determine the unique relation:

```javascript
let v = Vars("rel");
WOQL.interval_relation(v.rel,
  literal("2025-01-01", "xsd:date"), literal("2025-04-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"), literal("2025-07-01", "xsd:date"))
// v.rel = "meets"
```

This is useful for auditing temporal data:

```javascript
// Classify the relationship between every pair of reporting periods
let v = Vars("p1", "p2", "s1", "e1", "s2", "e2", "rel");
WOQL.and(
  WOQL.triple(v.p1, "period_start", v.s1),
  WOQL.triple(v.p1, "period_end", v.e1),
  WOQL.triple(v.p2, "period_start", v.s2),
  WOQL.triple(v.p2, "period_end", v.e2),
  WOQL.not_equals(v.p1, v.p2),
  WOQL.interval_relation(v.rel, v.s1, v.e1, v.s2, v.e2)
)
// Returns one row per pair with the Allen relation
```

---

## Producing Intervals from Dates and Durations

### From two explicit dates (half-open)

```javascript
let v = Vars("interval");
WOQL.interval(
  literal("2025-01-01", "xsd:date"),
  literal("2025-03-31", "xsd:date"),
  v.interval)
// v.interval = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval
```

### From two inclusive dates (reporting convention)

```javascript
let v = Vars("interval");
WOQL.interval(
  literal("2025-01-01", "xsd:date"),
  literal("2025-03-31", "xsd:date"),
  v.interval)
// v.interval = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval
// (internally: March 31 + 1 day = April 1)
```

### From a `dateRange` value via typecast

```javascript
let v = Vars("interval");
WOQL.typecast(
  literal("[2025-01-01, 2025-03-31]", "xdd:dateRange"),
  "xdd:dateTimeInterval",
  v.interval)
// v.interval = "2025-01-01T00:00:00Z/2025-04-01T00:00:00Z"^^xdd:dateTimeInterval
```

### From a start date and duration (form 2)

```javascript
let v = Vars("interval");
WOQL.typecast(
  literal("2025-01-01/P3M", "xsd:string"),
  "xdd:dateTimeInterval",
  v.interval)
// v.interval = "2025-01-01/P3M"^^xdd:dateTimeInterval
```

### From a duration and end date (form 3)

```javascript
let v = Vars("interval");
WOQL.typecast(
  literal("P3M/2025-04-01", "xsd:string"),
  "xdd:dateTimeInterval",
  v.interval)
// v.interval = "P3M/2025-04-01"^^xdd:dateTimeInterval
```

---

## `interval_relation_typed` — Allen's on Interval Values {#interval-relation-typed}

The `interval_relation` predicate requires four separate endpoint arguments. When your intervals are already stored or constructed as `xdd:dateTimeInterval` values, `interval_relation_typed` is more convenient — it takes the interval values directly.

| Predicate | Signature | Description |
|-----------|-----------|-------------|
| `interval_relation_typed` | `interval_relation_typed(Rel, X, Y)` | Allen's algebra on two `xdd:dateTimeInterval` values |

All 13 Allen relations work identically. The predicate internally decomposes each interval into start/end endpoints and delegates to the same logic as `interval_relation`.

### Example: Typed Validation — Q1 meets Q2

```javascript
WOQL.interval_relation_typed(
  literal("meets", "xsd:string"),
  literal("2025-01-01/2025-03-31", "xdd:dateTimeInterval"),
  literal("2025-04-01/2025-06-30", "xdd:dateTimeInterval"))
// Succeeds
```

### Example: Typed Classification

```javascript
let v = Vars("rel");
WOQL.interval_relation_typed(v.rel,
  literal("2025-01-01/2025-03-31", "xdd:dateTimeInterval"),
  literal("2025-04-01/2025-06-30", "xdd:dateTimeInterval"))
// v.rel = "meets"
```

### Example: DateTime Intervals (Sub-Day Precision)

```javascript
let v = Vars("rel");
WOQL.interval_relation_typed(v.rel,
  literal("2025-03-15T08:00:00Z/2025-03-15T12:00:00Z", "xdd:dateTimeInterval"),
  literal("2025-03-15T12:00:00Z/2025-03-15T17:00:00Z", "xdd:dateTimeInterval"))
// v.rel = "meets" — morning shift meets afternoon shift
```

### Python Example

```python
from terminusdb_client.woqlquery import WOQLQuery as WOQL

q1 = {"@type": "xdd:dateTimeInterval", "@value": "2025-01-01/2025-03-31"}
q2 = {"@type": "xdd:dateTimeInterval", "@value": "2025-04-01/2025-06-30"}

result = client.query(WOQL().interval_relation_typed("meets", q1, q2))
assert len(result["bindings"]) == 1
```

### When to Use Which Predicate

| Scenario | Predicate | Why |
|----------|-----------|-----|
| Endpoints already in separate variables | `interval_relation(rel, xs, xe, ys, ye)` | No need to construct intervals first |
| Intervals stored as `xdd:dateTimeInterval` | `interval_relation_typed(rel, x, y)` | Cleaner — no unpacking needed |
| Intervals constructed from inclusive dates | `interval_relation_typed(rel, x, y)` | Construct intervals from inclusive dates with `interval` |

---

## Complete Workflow — Financial Quarterly Reporting

This example demonstrates the full cycle: reporting periods expressed as inclusive date ranges, converted to half-open intervals for Allen's algebra, then converted back for display. It uses `interval_relation_typed` for the temporal checks.

```javascript
let v = Vars("q1", "q2", "q3", "q4", "rel_q1_q2",
             "q1_range", "q2_range");

WOQL.and(
  // Step 1: Construct intervals from inclusive reporting dates
  WOQL.interval(
    literal("2025-01-01", "xsd:date"),
    literal("2025-03-31", "xsd:date"), v.q1),
  WOQL.interval(
    literal("2025-04-01", "xsd:date"),
    literal("2025-06-30", "xsd:date"), v.q2),
  WOQL.interval(
    literal("2025-07-01", "xsd:date"),
    literal("2025-09-30", "xsd:date"), v.q3),
  WOQL.interval(
    literal("2025-10-01", "xsd:date"),
    literal("2025-12-31", "xsd:date"), v.q4),

  // Step 2: Verify temporal relationships using typed intervals
  WOQL.interval_relation_typed("meets", v.q1, v.q2),   // Q1 meets Q2
  WOQL.interval_relation_typed("meets", v.q2, v.q3),   // Q2 meets Q3
  WOQL.interval_relation_typed("meets", v.q3, v.q4),   // Q3 meets Q4
  WOQL.interval_relation_typed("before", v.q1, v.q3),  // Q1 is before Q3

  // Step 3: Classify the relationship between Q1 and Q2
  WOQL.interval_relation_typed(v.rel_q1_q2, v.q1, v.q2),
  // v.rel_q1_q2 = "meets"

  // Step 4: Convert back to inclusive dateRange for display
  WOQL.typecast(v.q1, "xdd:dateRange", v.q1_range),
  WOQL.typecast(v.q2, "xdd:dateRange", v.q2_range)
  // v.q1_range = "[2025-01-01, 2025-03-31]"^^xdd:dateRange
  // v.q2_range = "[2025-04-01, 2025-06-30]"^^xdd:dateRange
)
```

The key insight: inclusive dates go in, Allen's algebra verifies the temporal structure on the half-open representation, and inclusive dates come back out via typecasting. The half-open conversion happens transparently inside the interval type.
