Persistence
Persistence :: # (opaque)
Version 1 native persistence for text and native calendar descriptions, plus POSIX boundary/displacement/span/coverage values and IXDTF interpretation snapshots, resolved civil boundaries and complete selections. The JSON envelope has seven required string fields: format, version, kind, profile, axis, unit and payload. Format is roc-time; version is 1. Unknown metadata errors before temporal payload interpretation. No private records or compiler union encoding leak.
Text declarations use their declared canonical semantic text/profile and axis/unit none: they are source declarations, not resolved snapshots. This preserves resolution, qualifiers, UTC/local forms and ordered annotations; original spelling is not preserved. Declaration decoding never resolves zones. Rule/event/cursor kinds remain unsupported.
IxdtfSnapshot uses ixdtf-strict-snapshot-v1, axis posix-1970, unit microsecond. It stores the canonical IXDTF source, strict-v1 interpretation policy, saved boundary/offset and complete optional ZoneRules definition, including actual transitions, global offset bounds and provenance. Provider labels are not substitutes for the table; provenance is retained, not authenticated. Load validates the definition through its native constructor, resolves once using only that saved context and checks the saved boundary/offset. Subsequent getters read the restored result; unsupported calendar presentation remains unsupported. No host database, registry or network is consulted.
Snapshot payloads are flat JSON arrays of strings; the exact field order is documented in PersistenceSnapshot. Limits are 1024 transitions, 4096 combined rule metadata bytes, 49152 payload bytes and 65536 complete envelope bytes. Construction limit failures return InvalidSnapshot(TooLarge). Decode also distinguishes TooManyTransitions from byte limits; nothing is truncated. Construction checks transition/metadata limits before encoding, then caches canonical payload text after the escaped envelope size check. Serialization and load validation are linear in bounded stored bytes and transitions, not denoted time. Equality/hashing include source and complete context; use the snapshot's same_position method for position comparison. Input/output and the restored table are materialized; allocation traffic is not retained memory.
ResolvedBoundary and ResolvedSelection use civil-boundary-snapshot-v1 and civil-selection-snapshot-v1, respectively, on posix-1970/microsecond. They retain exact local labels with native calendar identity and full ZoneRules. Boundary archives retain RequireUnique, First, Last or MatchingOffset and the stored position/offset. Selection archives retain both labels and complete canonical coverage, including empty or disconnected results. An unfinished selection cursor is not a snapshot and has no persistence kind. Decode validates source labels and rules, invokes the native resolver once, then rejects a stored-result mismatch. No earliest/latest envelope replaces selection coverage, and no fold choice is inferred. Exact local labels use the native fraction-6 calendar transport grammar; this does not assert source precision for a LocalDateTime. Field order is documented in PersistenceCivil. The profiles accept at most 1024 transitions and 1024 coverage members, with the same 4096 metadata/49152 payload/65536 envelope byte limits. Construction checks counts before encoding and returns InvalidCivilSnapshot(TooLarge) if unsupported; parsing distinguishes malformed sources, policies, contexts and stored-result mismatches. Load follows native interpretation costs under those finite caps. Formatting never enumerates the represented calendar span.
Native CalendarValue/QualifiedCalendarValue profiles retain Gregorian/Julian provider range and every supplied resolution without resolving upper bounds. Their axis/unit are none. Payloads use calendar;resolution;year followed by only supplied month/day/hour/minute/second fields. Fraction resolution adds digits;value after seconds (integer 120 with digits 3 means .120). Qualified payloads append | and semicolon-joined scope=qualifier entries; even an empty qualifier list retains |. Qualification order canonicalizes via the native constructor. Names are lowercase; integers have no leading zeros or + sign. These native grammars are not ISO/EDTF. Payload decoding is capped at 1024 bytes and eight qualifiers; native canonical outputs need less than 512 bytes.
Core kinds posix-boundary and posix-delta use profile posix-microseconds-v1, axis posix-1970, unit microsecond. Their payload is a canonical signed decimal STRING, including either I64 limit: no JSON floating-point precision loss. A delta is coordinate displacement, not physical elapsed SI time.
Envelope decoding is bounded to 65536 encoded bytes and seven string fields. Span payloads use start/end signed decimals (posix-half-open-span-v1). Coverage payloads join canonical spans with semicolons (posix-canonical-coverage-v1); empty text means empty coverage. Input must be strictly ordered, disjoint and non-touching. This native payload grammar is not ISO interval syntax. These profiles use axis posix-1970 and unit microsecond. Missing endpoints return IncompleteSpan; malformed separators or integers remain distinct. Construction accepts at most 1024 coverage members, preserving all or failing. Decode counts members before allocating/parsing their spans, so an input above 1024 members errors before content/canonical-order validation. Canonical output fits below 44000 bytes: each pair needs at most 41 bytes plus one separator. Coverage encoding/decoding is linear in members plus text bytes; joins avoid repeated growing string appends. Input storage and output are materialized. Encoding does not interpret descriptions or enumerate temporal domains. Generic parser_for/encoder_for hooks are intentionally unsupported: use parse/to_text for the versioned envelope. Generic JSON must not derive the private Value representation or mistake it for this persistence contract.
import time.Persistence
import time.Coverage
import time.PosixSpan
import time.PosixBoundary
expect {
first = PosixSpan.new(PosixBoundary.from_microseconds(0), PosixBoundary.from_microseconds(1))?
second = PosixSpan.new(PosixBoundary.from_microseconds(2), PosixBoundary.from_microseconds(3))?
stored = Persistence.new(Coverage(Coverage.from_spans([first, second])))?
restored = Persistence.parse(Persistence.to_text(stored))?
match Persistence.value(restored) {
Coverage(coverage) => !Coverage.contains(coverage, PosixBoundary.from_microseconds(1))
_ => False
}
}
value : Persistence -> Value
to_inspect : Persistence -> Str
Value : [ResolvedBoundary(ResolvedBoundary), ResolvedSelection(ResolvedSelection), IxdtfSnapshot(Snapshot), EdtfDate(EdtfDate), OffsetTimestamp(OffsetTimestamp), ExactInterval(ExactInterval), Ixdtf(Ixdtf), RfcDateTime(RfcDateTime), RfcDuration(RfcDuration), RfcPeriod(RfcPeriod), PosixBoundary(PosixBoundary), PosixDelta(PosixDelta), PosixSpan(PosixSpan), Coverage(Coverage), CalendarValue(CalendarValue), QualifiedCalendarValue(QualifiedCalendarValue)]
Error : [InvalidCivilSnapshot(Error), InvalidSnapshot(Error), InvalidCalendarValue(Error), InvalidQualifiedCalendarValue(Error), Envelope(Error), UnknownFormat(Str), UnknownVersion(Str), UnknownKind(Str), UnsupportedProfile(Str), UnsupportedAxis(Str), UnsupportedUnit(Str), InvalidEdtfDate(Error), InvalidOffsetTimestamp(Error), InvalidExactInterval(Error), InvalidIxdtf(Error), InvalidRfcDateTime(Error), InvalidRfcDuration(Error), InvalidRfcPeriod(Error), InvalidInteger, OutOfRange, MalformedSpan, IncompleteSpan, InvalidSpan([EmptySpan, ReversedBounds]), NonCanonicalCoverage, TooManyMembers]