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§
- Calendar
Date Time - 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’sVarExpandSpec/IndexSeekSpec). - Duration
Fields - Raw, not-yet-normalized inputs to
duration({...})/duration('...')construction – onef64per Cypher map key (0.0when absent), kept as a struct (not 10 positionalf64args) so call sites read asyears: 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 (soduration.between(date(), date())is alwaysPT0S, never a few-microseconds-off nonzero duration from two independentnow()reads); capturing onechrono::Utc::now()and deriving every field from it (not onenow()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 ofPropertyValue::DateTime’s ownzone: marsdb_graph::model::TzIdfield (same reasoning asDurationPartsbelow: this module doesn’t depend onmarsdb_graph), translated at theexecutor.rsboundary.
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 + b– not a re-cascade throughnormalize_ duration(months/days add directly, no re-derivation viaAVG_MONTH_DAYS), matching the TCK’s “add two already-normalized durations” examples, which sum months and days independently and only ever carry betweenseconds/nanos(via the exacti128total, avoiding the sign-mismatch bug a naivea.nanos + b.nanoswould hit when the two operands’secondssigns differ). ReturnsNoneif any component would overflow its persisted integer representation. - add_
duration_ to_ date - Adds a
Durationto aDatevia 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:truefordate - 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 (samechecked_add_months/checked_sub_monthsclamping asadd_duration_to_date), thendays/seconds/nanosadded as one exact nanosecond count that carries across day boundaries (unlikeDate, which has no time-of- day to carry into – aLocalDateTime/DateTimedoes, so nothing here gets truncated the wayadd_duration_to_date’sseconds/nanosdo). Operates on the local wall-clock reading –DateTimecallers passepoch_seconds + offset_secondsin and subtractoffset_secondsback 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/LocalTimehave 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 – onlyseconds/nanosapply – rather than erroring, so this never fails (Optionelsewhere in this module means “can overflow”; wrapping never can).- capture_
now - combine_
date_ and_ time - Combines an
(epoch_day, nanos_of_day)pair intoLocalDateTime’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 aDate– the “forward” (date -> components) half of ISO week/quarter calendar math; the “backward” half (week/dayOfWeek/quarter/dayOfQuarter/ordinalDay-> date) lives inepoch_day_from_week_fields/epoch_day_from_ordinal_ fields/epoch_day_from_quarter_fieldsbelow. ReturnsNonefor any property name this doesn’t recognize (the caller treats that the same as a missing property, matching every other.propaccess in this codebase).- date_
time_ calendar_ component d.<prop>component access forLocalDateTime/DateTime’s calendar fields (year,month, …,dayOfQuarter) – delegates straight todate_componenton the instant’s calendar day, since the calendar math is identical toDate’s.- date_
time_ clock_ component d.<prop>component access forLocalDateTime/DateTime’s time-of-day fields (hour, …,nanosecond) – delegates tolocal_time_componenton the instant’s nanos-of-day, folding in the caller-supplied sub-secondnanosremainder (epoch_secondsalone 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 fixedOffset, subtracts it to get the UTC instantDateTimeactually stores (see its doc comment); for aNamedzone, resolves the real, DST-aware offset for this specific local date-time viachrono-tz(the same zone can mean a different offset on a different date, which is why this needs the full calendar contextresolve_offsetalone doesn’t have). - duration_
between - duration_
component d.<prop>component access for aDuration– 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. forduration({years: 1, months: 4, ...})(16 total months),d.yearsis16 / 12 = 1andd.monthsis16itself, not4. Verified against every field in Temporal5’s “accessors for duration” scenario. The*OfXfields (monthsOfYear,secondsOfMinute, …) are each the same computation’s remainder instead of its quotient – literally “whatd.<prop>would be, mod the next unit up”.seconds/nanosare stored the same way real Cypher’s ownDurationstores them (mirroring Java’sDuration):secondscarries the whole sign,nanosis always non-negative (0..999_999_999) – seePropertyValue::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 negativenanos(-23H-59M-59.9S‘s stored form isseconds: -86400, nanos: 100_000_000; re-splitting-86399.9svia truncating division gives the wrongseconds: -86399, nanosecondsOfSecond: -900_000_000instead, TCK’s Temporal10[1]).hours/minutes/seconds(and their-OfHour/-OfMinutecousins) only ever dividesecondsitself (never touchnanos– a whole hour/minute can’t hide inside a sub-second remainder);milliseconds/microseconds/nanoseconds(the fine-grained totals, not-OfSecondsplits) are the one place that legitimately combines both fields, sincenanos’ own always-non-negative convention means simple addition (nottotal_nsdivision-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 ofdate_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 ofdate_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_yearis 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 inPnYnMnDTnHnMn.fSorder (neverW, even thoughduration({weeks: 1})accepts it as an input unit – weeks fold intodaysduring normalization and never come back out, matching everytoString(duration(...))example in the TCK). Each component is a straight divmod of the sign-independent whole – a negativemonths/days/secondsprints 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 viaformat_date, time half via the sameformat_time_of_dayruleLocalTime/Timeuse (seconds/fraction only shown when non-zero).- format_
local_ time - format_
offset - Formats an offset as Cypher’s canonical text:
Zfor UTC, else[+-]HH:MM(extended with:SSonly 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:07example). - format_
time - local_
date_ time_ from_ fields - Builds a naive (zone-less)
(epoch_seconds, nanos)instant from calendar + time-of-day fields – shared bylocaldatetime({...})’s map form and (before the UTC offset adjustment)datetime({...})’s. - local_
time_ component d.<prop>component access shared byLocalTimeand (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-secondnanos) – range-checked the same waydate_from_mapchecks year/month/day,Nonefor 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 (viaAVG_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 everyduration(...)example in the TCK’s Temporal1/Temporal2 feature files. Never the other direction (seconds never cascade into days –duration({hours: 40})staysPT40H, notP1DT16H; a “day” isn’t a fixed number of hours once timezones/DST exist, so real Cypher never makes that assumption even though MarsDB’s ownDatetype 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 to1), ISO week-dateYYYY-Www[-D]/YYYYWww[D](missing day defaults to1), ordinal-dateYYYY-DDD/YYYYDDD(seeparse_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 forTzId::Named’s round-trip display). - parse_
duration - Parses an ISO-8601 duration string (
P[nY][nM][nW][nD][T[nH][nM][nS]], eachnan optional-sign decimal) into rawDurationFields, then normalizes the same wayduration({...})does – construction from text and from a map are the same operation once the units are pulled apart, seenormalize_duration’s docs. - parse_
local_ date_ time - Parses
YYYY-MM-DDTHH:MM:SS.fff(and the compact/date-only-precision variantsparse_datealready supports for the date half) into a naive(epoch_seconds, nanos)instant. A date-only string (noT) is also accepted, reading as midnight – real Cypher’slocaldatetime('-999999999-01-01')(TCK Temporal10 [10]). - parse_
local_ time localtime('21:40:32.142')– a bare time-of-day, no offset allowed (a trailingZ/+HH:MMmakes the whole string fail the strict digit/:/.-only parse above and correctly returnNone, the same “reject, don’t guess” stance as every other malformed-input case in this module).- parse_
offset_ seconds Zor[+-]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. ReturnsNoneif 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') –Noneifsisn’t a zonechrono-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 aNamedzone’s real, DST-aware offset viachrono-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] resolvesEurope/Stockholmto+01:00in October and+02:00in July). Falls back to UTC (0) for a zone name that fails to parse – should never happen for a value MarsDB itself constructed (everyNamedzone is validated viaparse_timezone_namebefore 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(factoris1.0 / nfor division) – re-cascades through the sameAVG_MONTH_DAYS-based logicnormalize_durationuses (scaling a whole month by a non-integer factor produces a fractional month again, e.g.P1M / 2needs to become “15.2 days”, not stay a fractional month), so this calls the sharedcascadedirectly withmonths/dayspre-multiplied and the exactseconds+nanostotal pre-multiplied as onei128quantity (truncated, same “no phantom sub-nanosecond digit” reasoning asnormalize_duration’sextra_nanos).- set_
iso_ weekday - Moves
epoch_dayto the given ISO weekday (1=Monday..7=Sunday) within its own ISO week – thedayOfWeekoverride key on a.truncate('week', ...)result (date.truncate('week', d, {dayOfWeek: 2})is “the Tuesday ofd’s week”), not general week-date construction from a{year, week, dayOfWeek}triple with no existing anchor date (that’sepoch_day_from_week_fields).Nonefor an out-of-rangeday_of_week. - split_
epoch_ seconds - Decomposes total (possibly negative)
epoch_secondsinto an(epoch_day, nanos_of_day)pair –div_euclid/rem_euclid, not plain//%, so a pre-1970 instant (negativeepoch_seconds) still gets ananos_of_dayin0..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
unit–Nonefor any unit that isn’t a calendar-scale one (hour/minute/… apply to the time half, seetruncate_time_unit).millennium/century/decadefloor the year to the nearest boundary below it (2017 -> 2000,1984 -> 1900/1980) – plainyear - year.rem_euclid(N), correct for negative years too sincerem_euclidis always non-negative.week/weekYearuse the same ISO week-date math as.week/.weekYearcomponent access (date_component) – the Monday of that ISO week/week-year. - truncate_
time_ unit - Truncates a time-of-day down to the start of
unit–Nonefor any unit that isn’t a clock-scale one.daytruncates to midnight (0), the shared boundary between the date and time halves.
Type Aliases§
- Duration
Parts - The four independently-signed components of a normalized
Duration, matchingPropertyValue::Duration’s own fields exactly – a plain tuple alias, not a re-export of thePropertyValuevariant itself, since this module deliberately doesn’t depend onmarsdb_graph(see this file’s top-of-module doc comment on the crate split).