Repairing Timestamps
Some timestamps contain valid syntax but require an explicit decision before they can identify an instant:
- An RFC 9557 numeric offset can disagree with its named zone.
- An RFC 9557 zone-only wall time can be ambiguous during a fold.
- An RFC 9557 zone-only wall time can be nonexistent during a gap.
- An RFC 9110, RFC 850, or RFC 5322 weekday can disagree with its calendar date.
zdate rejects these conditions during parsing, every time. ZRepair.timestamp() repairs them afterward using an explicit policy for each accepted format and condition:
import {type TimestampRepairPolicy} from '@sajajs/zdate'
const repairPolicy = {
rfc9557: {
disagreement: 'offset',
ambiguity: 'default',
nonexistent: 'default'
},
rfc9110: 'date',
rfc850: 'date',
rfc5322: 'date'
} satisfies TimestampRepairPolicyFor an RFC 9557 disagreement, offset preserves the instant defined by the numeric offset while zone resolves the wall time using the named zone.
When a wall time is ambiguous, it happened twice; when it is nonexistent, it never happened at all. Either way the timestamp has two reasonable readings — one earlier, one later — and earlier and later choose between them. default uses zdate's standard wall-time resolution, matching JavaScript Date: the earlier reading for an ambiguity, the later for a nonexistent time.
For header dates, date corrects the weekday while weekday moves the calendar date to the nearest matching weekday.
Each policy is optional. Applications authorize only the repairs appropriate for their input contract; a condition without its policy returns the original error rather than repairing.
Parsing Fails Loudly, Repair Is Explicit
zdate never repairs a timestamp during parsing. Repair is a visible action taken afterward — searchable in application code — and no configuration can quietly change what a timestamp means.
import {zdate, type ZDate, ZError, ZRepair} from '@sajajs/zdate'
const stored = '2023-01-15T10:00:00+02:00[Asia/Amman]' // Jordan's 2022 abolition of winter time
let date: ZDate
try {
date = zdate(stored)
} catch (error) {
if (!ZError.is(error, 'resolution'))
throw error
const repaired = ZRepair.timestamp(stored, repairPolicy, error)
if (repaired.error)
throw repaired.error
date = zdate(repaired.output)
}A 'resolution' ZError carries the data needed for repair. Passing the caught error to timestamp() avoids parsing the input again. If the error has no recognized repair data or belongs to another input, timestamp() reparses internally. Non-repairable parse failures are returned through error rather than thrown.
Repairing Stored Data
ZRepair.timestamp() also serves migration and maintenance jobs after time-zone rules change:
for (const row of timestamps) {
const repaired = ZRepair.timestamp(row.value, repairPolicy)
if (repaired.error)
logRepairFailure(row.id, repaired.error) // example of application function
else if (repaired.output !== repaired.input)
await updateTimestamp(row.id, repaired.output) // example of application function
}RepairedResult
RepairedResult has three possible outcomes:
erroris present: the input could not be repaired.output !== input: the timestamp was repaired.output === input: no resolution error was found.
The error is undefined if there is no error. The output is undefined if there is an error. Always check error first.
Coming from Temporal's offset and disambiguation options? The concept map translates each one to its policy equivalent.
