Skip to main content

Module temporal

Module temporal 

Source
Expand description

Calendar math and ISO-8601 text conversion for PropertyValue::Date/ PropertyValue::Duration – kept out of marsdb-graph deliberately (that crate stores the value, it doesn’t know Cypher’s construction/ formatting rules – see PropertyValue’s own doc comment) and out of executor.rs (which owns dispatching to these, not the arithmetic itself, matching the split apply_arith/compare already have from e.g. the planner).

Scope, honestly: DATE (calendar year/month/day, ISO week-date, and ordinal/quarter-date construction forms), DURATION, LOCAL TIME, TIME, LOCAL DATETIME, and DATETIME are all supported – but TIME/DATETIME only accept a fixed UTC offset ('+01:00', {timezone: '+01:00'}), never a named timezone ('Europe/Stockholm') – that needs a real IANA timezone database, deliberately out of scope (no DST/zone-rule awareness anywhere in this module). See the README’s “Cypher coverage” section for the exact list of what that leaves out of TCK’s expressions/temporal suite.

Structs§

CalendarDateTime
Calendar + time-of-day fields for localdatetime({...})/ datetime({...})’s map constructors – bundled into one struct (not 7 positional args) purely to stay under clippy’s argument-count cap, matching this codebase’s established convention for that lint (see e.g. executor.rs’s VarExpandSpec/IndexSeekSpec).
DurationFields
Raw, not-yet-normalized inputs to duration({...})/duration('...') construction – one f64 per Cypher map key (0.0 when absent), kept as a struct (not 10 positional f64 args) so call sites read as years: 12.0, ..Default::default() rather than an unlabeled tuple.
NowSnapshot
A single captured instant, pre-derived into every shape a no-arg date()/localtime()/time()/localdatetime()/datetime() call needs – real Cypher guarantees every such call within the same query returns the same value (so duration.between(date(), date()) is always PT0S, never a few-microseconds-off nonzero duration from two independent now() reads); capturing one chrono::Utc::now() and deriving every field from it (not one now() call per field) is what makes that guarantee hold even within a single construction.

Enums§

TzId
A DateTime’s zone – a plain, marsdb_graph-independent mirror of PropertyValue::DateTime’s own zone: marsdb_graph::model::TzId field (same reasoning as DurationParts below: this module doesn’t depend on marsdb_graph), translated at the executor.rs boundary.

Constants§

MAX_YEAR
MIN_YEAR
Cypher’s documented year range (java.time’s, which real Cypher mirrors). Every constructor validates against it; epoch days for this range (±365 billion) always fit i64 with room for nanosecond totals in i128.

Functions§

add_duration
Component-wise a + bnot a re-cascade through normalize_ duration (months/days add directly, no re-derivation via AVG_MONTH_DAYS), matching the TCK’s “add two already-normalized durations” examples, which sum months and days independently and only ever carry between seconds/nanos (via the exact i128 total, avoiding the sign-mismatch bug a naive a.nanos + b.nanos would hit when the two operands’ seconds signs differ). Returns None if any component would overflow its persisted integer representation.
add_duration_to_date
Adds a Duration to a Date via real calendar month arithmetic (checked_add_months/checked_sub_months, which clamps to the shorter month’s last day – e.g. Jan 31 + 1 month = Feb 28/29, not an error and not Mar 3) followed by a plain day offset. negate: true for date - duration (real Cypher’s other overload), reusing the same function rather than duplicating it with - in every arithmetic expression.
add_duration_to_local_date_time
LocalDateTime/DateTime + Duration – real calendar month arithmetic on the date part (same checked_add_months/ checked_sub_months clamping as add_duration_to_date), then days/seconds/nanos added as one exact nanosecond count that carries across day boundaries (unlike Date, which has no time-of- day to carry into – a LocalDateTime/DateTime does, so nothing here gets truncated the way add_duration_to_date’s seconds/ nanos do). Operates on the local wall-clock reading – DateTime callers pass epoch_seconds + offset_seconds in and subtract offset_seconds back out of the result, so month/day arithmetic happens against the calendar the user actually wrote, not the UTC instant (matches real Cypher: `datetime({…, timezone: ‘+05:00’})
add_duration_to_time
Time/LocalTime + Duration – wraps at the 24h boundary (Time/ LocalTime have no calendar, so there’s no “next day” to carry into). Real Cypher truncates a Duration’s calendar components (months/days) when adding it to a time-only value – only seconds/nanos apply – rather than erroring, so this never fails (Option elsewhere in this module means “can overflow”; wrapping never can).
capture_now
combine_date_and_time
Combines an (epoch_day, nanos_of_day) pair into LocalDateTime’s own (epoch_seconds, nanos) storage shape – shared by <type>. truncate()’s date+time recombination step.
combine_epoch_day_and_nanos_of_day
date_component
d.<prop> component access for a Date – the “forward” (date -> components) half of ISO week/quarter calendar math; the “backward” half (week/dayOfWeek/quarter/dayOfQuarter/ordinalDay -> date) lives in epoch_day_from_week_fields/epoch_day_from_ordinal_ fields/epoch_day_from_quarter_fields below. Returns None for any property name this doesn’t recognize (the caller treats that the same as a missing property, matching every other .prop access in this codebase).
date_time_calendar_component
d.<prop> component access for LocalDateTime/DateTime’s calendar fields (year, month, …, dayOfQuarter) – delegates straight to date_component on the instant’s calendar day, since the calendar math is identical to Date’s.
date_time_clock_component
d.<prop> component access for LocalDateTime/DateTime’s time-of-day fields (hour, …, nanosecond) – delegates to local_time_component on the instant’s nanos-of-day, folding in the caller-supplied sub-second nanos remainder (epoch_seconds alone only has whole-second precision).
date_time_from_fields
Same as local_date_time_from_fields, but the wall-clock reading is in the given zone – for a fixed Offset, subtracts it to get the UTC instant DateTime actually stores (see its doc comment); for a Named zone, resolves the real, DST-aware offset for this specific local date-time via chrono-tz (the same zone can mean a different offset on a different date, which is why this needs the full calendar context resolve_offset alone doesn’t have).
duration_between
duration_component
d.<prop> component access for a Duration – every field (years, quarters, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is simply the whole duration re-expressed in that one unit alone, truncated towards zero – not a calendar-style “the months-of-year part” breakdown. E.g. for duration({years: 1, months: 4, ...}) (16 total months), d.years is 16 / 12 = 1 and d.months is 16 itself, not 4. Verified against every field in Temporal5’s “accessors for duration” scenario. The *OfX fields (monthsOfYear, secondsOfMinute, …) are each the same computation’s remainder instead of its quotient – literally “what d.<prop> would be, mod the next unit up”. seconds/nanos are stored the same way real Cypher’s own Duration stores them (mirroring Java’s Duration): seconds carries the whole sign, nanos is always non-negative (0..999_999_999) – see PropertyValue::Duration’s own docs. Component accessors must read off these two raw fields directly, not recombine them into one signed total and re-split – that would silently reintroduce a negative nanos (-23H-59M-59.9S‘s stored form is seconds: -86400, nanos: 100_000_000; re-splitting -86399.9s via truncating division gives the wrong seconds: -86399, nanosecondsOfSecond: -900_000_000 instead, TCK’s Temporal10 [1]). hours/minutes/seconds (and their -OfHour/-OfMinute cousins) only ever divide seconds itself (never touch nanos – a whole hour/minute can’t hide inside a sub-second remainder); milliseconds/microseconds/nanoseconds (the fine-grained totals, not -OfSecond splits) are the one place that legitimately combines both fields, since nanos’ own always-non-negative convention means simple addition (not total_ns division-then-truncation) already gives the right signed result.
duration_in_days
duration_in_months
duration_in_seconds
epoch_day_from_ordinal_fields
Constructs an epoch-day from a calendar year plus an ordinal day (1..=365/366) – the inverse of date_component’s "ordinalDay".
epoch_day_from_quarter_fields
Constructs an epoch-day from a calendar year, quarter (1..=4), and day-of-quarter (1-based) – the inverse of date_component’s "quarter"/"dayOfQuarter".
epoch_day_from_week_fields
Constructs an epoch-day from ISO week-date fields – the inverse of date_component’s "weekYear"/"week"/"dayOfWeek" accessors. week_year is the ISO week-numbering year, not necessarily the calendar year of the resulting date (they diverge near a year boundary – e.g. week-year 1817 week 1 day 2 is calendar date 1816-12-31, TCK’s Temporal1 [1]).
epoch_day_from_ymd
epoch_seconds_and_millis
format_date
format_date_time
format_duration
Renders (months, days, seconds, nanos) as MarsDB’s canonical ISO-8601 duration text – always in PnYnMnDTnHnMn.fS order (never W, even though duration({weeks: 1}) accepts it as an input unit – weeks fold into days during normalization and never come back out, matching every toString(duration(...)) example in the TCK). Each component is a straight divmod of the sign-independent whole – a negative months/days/seconds prints its own - (P-6M-15D...), not one shared sign prefix, matching the TCK’s mixed- sign examples exactly (see Temporal8’s duration-subtraction table).
format_local_date_time
YYYY-MM-DDTHH:MM[:SS[.fraction]] – date half via format_date, time half via the same format_time_of_day rule LocalTime/Time use (seconds/fraction only shown when non-zero).
format_local_time
format_offset
Formats an offset as Cypher’s canonical text: Z for UTC, else [+-]HH:MM (extended with :SS only when the offset has a non-zero seconds component – real offsets are almost always whole minutes, but the TCK’s timezone grep found at least one -02:05:07 example).
format_time
local_date_time_from_fields
Builds a naive (zone-less) (epoch_seconds, nanos) instant from calendar + time-of-day fields – shared by localdatetime({...})’s map form and (before the UTC offset adjustment) datetime({...})’s.
local_time_component
d.<prop> component access shared by LocalTime and (for its own wall-clock time-of-day fields) Time/LocalDateTime/DateTime.
local_time_nanos_from_fields
Builds a LocalTime’s nanos-of-day from calendar-style fields (localtime({hour, minute, second, nanosecond})’s already-summed sub-second nanos) – range-checked the same way date_from_map checks year/month/day, None for anything out of range.
negate_duration
normalize_duration
Folds raw (possibly fractional, possibly negative) field values into PropertyValue::Duration’s normalized (months, days, seconds, nanos) form. The cascade only ever flows one direction – years into months, a fractional month’s remainder into days (via AVG_MONTH_DAYS – the only place that average is used), a fractional day’s remainder into seconds, sub-second fields into nanoseconds – matching Neo4j’s own documented normalization, verified line-by-line against every duration(...) example in the TCK’s Temporal1/Temporal2 feature files. Never the other direction (seconds never cascade into days – duration({hours: 40}) stays PT40H, not P1DT16H; a “day” isn’t a fixed number of hours once timezones/DST exist, so real Cypher never makes that assumption even though MarsDB’s own Date type is timezone-naive).
parse_date
Parses every date string form MarsDB supports: the plain calendar forms YYYY-MM-DD/YYYYMMDD/YYYY-MM/YYYYMM/YYYY (missing month/day default to 1), ISO week-date YYYY-Www[-D]/YYYYWww[D] (missing day defaults to 1), ordinal-date YYYY-DDD/YYYYDDD (see parse_week_or_ordinal_date), and ISO 8601 expanded years – an explicit leading sign with up to 9 year digits ('-999999999-01-01', '+999999999-12-31', TCK Temporal10 [9]/[10]). The sign is stripped here and applied to whichever year field the body then parses (calendar, week, or ordinal alike).
parse_date_time
Same date+time parse as parse_local_date_time, plus a required zone on the time half – either a fixed offset (+01:00), a bracketed named zone with no explicit offset ([Europe/London], the true offset derived from the zone for this local date-time, TCK’s Temporal2 [6]), or both together (+02:00[Europe/Stockholm], the explicit offset is trusted for the instant and the bracket is kept only for TzId::Named’s round-trip display).
parse_duration
Parses an ISO-8601 duration string (P[nY][nM][nW][nD][T[nH][nM][nS]], each n an optional-sign decimal) into raw DurationFields, then normalizes the same way duration({...}) does – construction from text and from a map are the same operation once the units are pulled apart, see normalize_duration’s docs.
parse_local_date_time
Parses YYYY-MM-DDTHH:MM:SS.fff (and the compact/date-only-precision variants parse_date already supports for the date half) into a naive (epoch_seconds, nanos) instant. A date-only string (no T) is also accepted, reading as midnight – real Cypher’s localdatetime('-999999999-01-01') (TCK Temporal10 [10]).
parse_local_time
localtime('21:40:32.142') – a bare time-of-day, no offset allowed (a trailing Z/+HH:MM makes the whole string fail the strict digit/:/.-only parse above and correctly return None, the same “reject, don’t guess” stance as every other malformed-input case in this module).
parse_offset_seconds
Z or [+-]HH[:MM[:SS]] / compact [+-]HHMM[SS] -> whole seconds east of UTC.
parse_time
time('21:40:32.142+01:00') – a time-of-day with a required offset. Returns None if the string has no offset at all, or if it carries a bracketed named-zone suffix ([Europe/Stockholm]) – the caller (Executor::call_builtin’s "time" arm) checks for [ itself first and raises a specific “named zones aren’t supported” error rather than this generic parse failure, but this function still refuses to silently ignore/misparse the bracket if called directly.
parse_timezone_name
Parses an IANA timezone name ('Europe/Stockholm') – None if s isn’t a zone chrono-tz’s embedded database recognizes.
resolve_offset
Resolves a TzId’s real UTC offset (seconds east of UTC) at a given UTC instant – Offset’s value directly, or a Named zone’s real, DST-aware offset via chrono-tz’s embedded IANA database (the same zone name resolves to a different offset depending on which instant this is called with – there’s no single fixed “the” offset for a named zone, e.g. TCK’s Temporal1 [10] resolves Europe/Stockholm to +01:00 in October and +02:00 in July). Falls back to UTC (0) for a zone name that fails to parse – should never happen for a value MarsDB itself constructed (every Named zone is validated via parse_timezone_name before being stored), but this function can’t return an error, so degrade gracefully rather than panic on a hypothetical corrupt/foreign-written value.
scale_duration
duration * factor / duration / factor (factor is 1.0 / n for division) – re-cascades through the same AVG_MONTH_DAYS-based logic normalize_duration uses (scaling a whole month by a non-integer factor produces a fractional month again, e.g. P1M / 2 needs to become “15.2 days”, not stay a fractional month), so this calls the shared cascade directly with months/days pre-multiplied and the exact seconds+nanos total pre-multiplied as one i128 quantity (truncated, same “no phantom sub-nanosecond digit” reasoning as normalize_duration’s extra_nanos).
set_iso_weekday
Moves epoch_day to the given ISO weekday (1=Monday..7=Sunday) within its own ISO week – the dayOfWeek override key on a .truncate('week', ...) result (date.truncate('week', d, {dayOfWeek: 2}) is “the Tuesday of d’s week”), not general week-date construction from a {year, week, dayOfWeek} triple with no existing anchor date (that’s epoch_day_from_week_fields). None for an out-of-range day_of_week.
split_epoch_seconds
Decomposes total (possibly negative) epoch_seconds into an (epoch_day, nanos_of_day) pair – div_euclid/rem_euclid, not plain //%, so a pre-1970 instant (negative epoch_seconds) still gets a nanos_of_day in 0..NANOS_PER_DAY (Rust’s % on a negative dividend returns a negative remainder, which would put the “same calendar day” one day off).
sub_duration
truncate_date_unit
Truncates a calendar date down to the start of unitNone for any unit that isn’t a calendar-scale one (hour/minute/… apply to the time half, see truncate_time_unit). millennium/ century/decade floor the year to the nearest boundary below it (2017 -> 2000, 1984 -> 1900/1980) – plain year - year.rem_euclid(N), correct for negative years too since rem_euclid is always non-negative. week/weekYear use the same ISO week-date math as .week/.weekYear component access (date_component) – the Monday of that ISO week/week-year.
truncate_time_unit
Truncates a time-of-day down to the start of unitNone for any unit that isn’t a clock-scale one. day truncates to midnight (0), the shared boundary between the date and time halves.

Type Aliases§

DurationParts
The four independently-signed components of a normalized Duration, matching PropertyValue::Duration’s own fields exactly – a plain tuple alias, not a re-export of the PropertyValue variant itself, since this module deliberately doesn’t depend on marsdb_graph (see this file’s top-of-module doc comment on the crate split).