Temporal Functions (Dates, Times, Durations)
LoraDB supports the Cypher temporal model end-to-end — see Temporal Data Types for the type details. Each value is first-class: store it as a property, compare it, do arithmetic on it.
Overview
| Goal | Function |
|---|---|
| Current date/time | temporal.today(), temporal.now('date'), temporal.now() / now(), temporal.now('time'), temporal.now('local_time'), temporal.now('local_datetime') |
| Parse ISO string | '…'::DATE, '…'::DATETIME, etc. |
| From components | {year, month, day}::DATE, … |
| Construct duration | 'P…'::DURATION, {days, hours, …}::DURATION |
| Truncate | temporal.truncate(unit, value) |
| Difference | temporal.between(a, b), temporal.in_days(a, b) |
| Component access | dt.year, dt.month, dt.hour, dur.days … |
| Add/subtract | date + duration, datetime - datetime |
Temporal types at a glance
| Type | Components | Timezone |
|---|---|---|
Date | year, month, day | — |
Time | hour, minute, second, nanosecond | UTC offset |
LocalTime | hour, minute, second, nanosecond | — |
DateTime | Date + Time fields | UTC offset |
LocalDateTime | Date + LocalTime fields | — |
Duration | months, days, seconds, nanoseconds | — |
Construction And Current Time
Construct temporal values with casts. value::TYPE is compact for
handwritten Cypher, while CAST(value AS TYPE) is also supported by the
Cypher grammar. TRY_CAST(value AS TYPE) returns null instead of
reporting a conversion error.
The zero-argument current-value helpers also have bare aliases:
now() for temporal.now(),
timestamp() for
temporal.timestamp(), and
timezone() for
temporal.timezone().
There are two separate jobs here:
- Current-time helpers create a value from the database clock.
- Casts create or convert a value from query text, maps, parameters, or other expressions.
Avoid wrapping an already-cast value in an old constructor-shaped helper.
For example, write $value::DATETIME, not datetime($value::DATETIME)
or temporal.datetime($value::DATETIME).
Current-time helpers
| Helper | Returns | Use when |
|---|---|---|
temporal.today() | DATE | You need the current calendar day. |
temporal.now('date') | DATE | Equivalent current-day form when the kind is parameterized. |
temporal.now() / now() | DATETIME | You need the current instant with timezone offset. |
temporal.now('time') | TIME | You need only the current time-of-day with offset. |
temporal.now('local_time') | LOCAL_TIME | You need a wall-clock time without timezone. |
temporal.now('local_datetime') | LOCAL_DATETIME | You need date and wall-clock time without timezone. |
temporal.timestamp() / timestamp() | INTEGER | You need Unix epoch milliseconds. |
temporal.timezone() / timezone() | STRING | You need the database timezone label, currently UTC. |
Use temporal.now() for stored instants such as created_at and
updated_at. Use temporal.today() for date-only concepts such as
birthdays, billing days, and cohort dates. Use local variants only when
the value is intentionally a wall-clock value rather than an absolute
instant.
Date
| Form | Example |
|---|---|
| Current day | temporal.today() |
| ISO string | '2024-01-15'::DATE |
| Map | {year: 2024, month: 1, day: 15}::DATE |
| CAST form | CAST('2024-01-15' AS DATE) |
RETURN temporal.today(); // today
RETURN '2024-01-15'::DATE; // 2024-01-15
RETURN {year: 2024, month: 1, day: 15}::DATE; // 2024-01-15
RETURN TRY_CAST($maybe_date AS DATE) // null on invalid inputDateTime
| Form | Example |
|---|---|
| Current instant | temporal.now() / now() |
| ISO string | '2024-01-15T10:00:00Z'::DATETIME |
| Map | {year, month, day, hour, minute, second, millisecond, timezone}::DATETIME |
| Local current instant | temporal.now('local_datetime') |
RETURN '2024-01-15T10:00:00Z'::DATETIME;
RETURN {year: 2024, month: 1, day: 15, hour: 10, minute: 0}::DATETIME;
RETURN '2024-01-15T10:00:00+02:00'::DATETIMETime / LocalTime / LocalDateTime
RETURN '12:34:56'::TIME; // with UTC offset (default Z)
RETURN '12:34:56+02:00'::TIME;
RETURN '12:34:56'::LOCAL_TIME; // no timezone
RETURN '2024-01-15T10:00:00'::LOCAL_DATETIME;
RETURN temporal.now('time');
RETURN temporal.now('local_time');
RETURN temporal.now('local_datetime')duration
ISO 8601 string or a component map.
RETURN 'P30D'::DURATION; // 30 days
RETURN 'P1Y2M3DT4H5M6S'::DURATION; // full form
RETURN 'PT90M'::DURATION; // 90 minutes
RETURN {years: 1, months: 2, days: 3}::DURATION; // equivalent map form
RETURN CAST('PT90M' AS DURATION) // CAST formQuery casts vs parameters
Every binding ships a helper so you can pass typed values in host-language parameter maps without writing query casts:
// Node.js / WASM
import { datetime, duration } from "@loradb/lora-node";
await db.execute(
"CREATE (:Event {at: $at, len: $len})",
{ at: datetime("2026-05-01T09:00:00Z"), len: duration("PT90M") }
);
See Node → typed helpers and Python → parameters.
Component access
Temporal values expose components via property access.
RETURN '2024-01-15'::DATE.year; // 2024
RETURN '2024-01-15'::DATE.month; // 1
RETURN '2024-01-15T10:30:00Z'::DATETIME.hour; // 10
RETURN '2024-01-15T10:30:45Z'::DATETIME.second; // 45
RETURN 'P30D'::DURATION.days; // 30
RETURN 'P1Y'::DURATION.months // 12Available: .year, .month, .day, .hour, .minute, .second,
.millisecond, .days, .months, .years, .hours, .minutes,
.seconds.
Build a year-month key
MATCH (e:Event)
RETURN e.at.year AS year,
e.at.month AS month,
count(*) AS events
ORDER BY year, monthTruncation
Reduce a temporal value to a coarser unit.
| Function | Supported units |
|---|---|
temporal.truncate(unit, date) | "year", "month", "day" |
temporal.truncate(unit, datetime) | "year", "month", "day", "hour" |
RETURN temporal.truncate('month', '2024-01-15'::DATE); // 2024-01-01
RETURN temporal.truncate('year', '2024-07-01'::DATE); // 2024-01-01
RETURN temporal.truncate('hour', '2024-01-15T10:42:00Z'::DATETIME)
// 2024-01-15T10:00:00ZBucketing rows
MATCH (e:Event)
RETURN temporal.truncate('month', e.at) AS month, count(*) AS events
ORDER BY monthMATCH (r:Request)
RETURN temporal.truncate('hour', r.at) AS hour, count(*) AS hits
ORDER BY hourArithmetic
Date + Duration→DateDateTime + Duration→DateTimeDateTime - DateTime→Duration
Duration arithmetic preserves calendar semantics: months and days are stored separately from seconds.
RETURN '2024-01-15'::DATE + 'P30D'::DURATION
; // 2024-02-14
RETURN '2024-01-15T00:00:00Z'::DATETIME + 'PT36H'::DURATION
; // 2024-01-16T12:00:00Z
RETURN '2024-12-31T00:00:00Z'::DATETIME - '2024-01-01T00:00:00Z'::DATETIME
// P365D (a Duration)Calendar vs fixed durations
'P1M'::DURATION is "one month" — a variable number of days. 'P30D'::DURATION
is exactly 30 days.
RETURN '2024-01-31'::DATE + 'P1M'::DURATION; // 2024-02-29 (leap year)
RETURN '2024-01-31'::DATE + 'P30D'::DURATION // 2024-03-01temporal.between / temporal.in_days
RETURN temporal.between('2024-01-01'::DATE, '2024-12-31'::DATE)
; // P365D (Duration)
RETURN temporal.in_days('2024-01-01'::DATE, '2024-04-10'::DATE)
// 100temporal.in_days is for DATE values. For DATETIME values, use
temporal.between(a, b).days when you need the day component.
Comparison
Comparable within the same type using <, <=, >, >=, =, <>.
Cross-type comparisons (e.g. Date vs DateTime) return null.
MATCH (e:Event)
WHERE e.at >= temporal.now() AND e.at < temporal.now() + 'P7D'::DURATION
RETURN e
ORDER BY e.atMATCH (p:Person)
WHERE p.born < '1900-01-01'::DATE
RETURN p.name, p.bornStoring temporal values
Temporals serialise tagged: {"kind": "date", "iso": "2024-01-15"} etc.
(see Temporal Data Types). They
round-trip cleanly through CREATE and MATCH.
CREATE (e:Event {
title: 'Launch',
at: '2026-05-01T09:00:00Z'::DATETIME,
runs_for: 'PT90M'::DURATION,
day: '2026-05-01'::DATE
});
MATCH (e:Event)
RETURN e.title,
e.at,
e.at + e.runs_for AS ends_atCommon patterns
Events in the next week
MATCH (e:Event)
WHERE e.at >= temporal.now()
AND e.at < temporal.now() + 'P7D'::DURATION
RETURN e
ORDER BY e.atEvents in a month
MATCH (e:Event)
WHERE temporal.truncate('month', e.at) = '2026-05-01'::DATE
RETURN eAge from birthday
MATCH (p:Person)
RETURN p.name,
temporal.in_days(p.born, temporal.today()) / 365 AS approx_age_yearsRolling 30-day active users
MATCH (u:User)-[:VIEWED]->(:Page)
WHERE u.last_seen >= temporal.now() - 'P30D'::DURATION
RETURN count(DISTINCT u) AS active_30dSession length
MATCH (s:Session)
RETURN s.id, (s.ended - s.started) AS duration
ORDER BY duration DESCFirst / last event per user
MATCH (u:User)-[:DID]->(e:Event)
RETURN u.id,
min(e.at) AS first_event,
max(e.at) AS last_eventCohorts by signup month
MATCH (u:User)
RETURN temporal.truncate('month', u.created) AS cohort,
count(*) AS signups
ORDER BY cohort"Since last seen" bucket
MATCH (u:User)
WITH u,
temporal.between(u.last_seen, temporal.now()).days AS days_away
RETURN CASE
WHEN days_away <= 1 THEN 'today'
WHEN days_away <= 7 THEN 'week'
WHEN days_away <= 30 THEN 'month'
ELSE 'dormant'
END AS freshness,
count(*) AS users
ORDER BY users DESCUses CASE to bucket a
continuous duration into named tiers.
Time-of-day histogram
MATCH (e:Event)
RETURN e.at.hour AS hour, count(*) AS events
ORDER BY hourComponent access on a DateTime returns integers — no string parsing
needed.
Recurring window — "same time next week"
MATCH (m:Meeting {id: $id})
RETURN m.start,
m.start + 'P7D'::DURATION AS next_week,
m.start + 'P14D'::DURATION AS two_weeksBuild ISO timestamp for serialisation
MATCH (e:Event)
RETURN e.id, e.at::STRING AS isoCAST(e.at AS STRING) / e.at::STRING on a DateTime emits a
round-trippable ISO 8601 string.
Edge cases
Mixing types
Date - DateTime, Time + Duration — not supported. Convert first to
matching types.
Timezone handling
DateTime carries a UTC offset. Compare DateTime values across zones
freely — they're normalised to UTC internally. LocalDateTime has no
zone; two LocalDateTime values compare by naive wall-clock order.
Strict ISO parsing
Non-ISO shapes (MM/DD/YYYY, RFC-2822, ISO week-dates) are rejected at
parse time. Normalise on the host side before passing to
'…'::DATE / '…'::DATETIME.
temporal.today() with no args — now vs wall clock
In WASM, temporal.today() resolves to Date.now() at millisecond precision —
nanosecond fields are zero. In native builds, it reflects the OS clock.
See WASM → gotchas.
Limitations
temporal.truncatesupports"year","month", and"day"forDATEvalues;"year","month","day", and"hour"forDATETIMEvalues.- Arithmetic between values of different temporal types
(e.g.
Date - Time) is not supported. Convert first. - Parsing is strict ISO 8601 — non-ISO shapes (
MM/DD/YYYY, RFC-2822) are rejected. - No component-access shortcuts on
Durationbeyond the listed fields.
See also
- Temporal Data Types — type reference.
- Scalars — underlying numeric components.
- WHERE — temporal predicates.
- Ordering — chronological sorting.
- Aggregation — bucketing with
temporal.truncate.