Serializing and Storing Temporal Values
How to serialize each Temporal type to the string and column that round-trips it losslessly. Part of Modern Temporal API.
Problem framing
Persisting a Temporal value is easy to get subtly wrong because each type carries different information and a database column rarely matches it exactly. Store a ZonedDateTime as a bare UTC timestamp and you lose the zone, so you can no longer reconstruct the user's wall clock after a rule change. Store a PlainDate in a timestamptz column and the driver bolts on a midnight and a zone, reintroducing the very ambiguity PlainDate exists to avoid. The rule is to serialize each type to the string form that round-trips it losslessly, and to pick the column that stores exactly those bits.
API reference
Every Temporal type has a toString() that emits a canonical, machine-parseable form, and a matching from() that round-trips it.
Approach A: legacy Date
With Date, the only safe serialization is toISOString() (UTC). It cannot represent a zone or a zoneless civil date, so applications historically stored a second column for the IANA zone alongside the timestamp.
const row = { at: new Date().toISOString(), zone: 'America/New_York' }; // two columns
Approach B: Temporal
Serialize with toString(), deserialize with from(). The zone rides inside the ZonedDateTime string, so a single text column reconstructs everything.
import { Temporal } from '@js-temporal/polyfill';
const s = zdt.toString(); // '2024-03-15T09:00:00-04:00[America/New_York]'
const back = Temporal.ZonedDateTime.from(s); // fully reconstructed, DST-aware
Production implementation
A tiny codec centralizes the mapping so every layer serializes consistently, and a JSON reviver rehydrates values on the way in.
import { Temporal } from '@js-temporal/polyfill';
export const codec = {
toJSON: (v: Temporal.ZonedDateTime | Temporal.PlainDate) => v.toString(),
reviveInstant: (s: string) => Temporal.Instant.from(s),
reviveZoned: (s: string) => Temporal.ZonedDateTime.from(s),
reviveDate: (s: string) => Temporal.PlainDate.from(s),
};
Edge cases
Gotchas & common pitfalls
Testing checklist
Frequently Asked Questions
How should I store a Temporal.ZonedDateTime in a database?
Serialize it with toString(), which yields a self-describing string like '2024-03-15T09:00:00-04:00[America/New_York]', and store it in a text column. That preserves the IANA zone, so you can reconstruct the exact wall-clock time even after future DST-rule changes — something a bare UTC timestamp cannot do.
Can I store a Temporal.PlainDate in a timestamp column?
Avoid it. A timestamp or timestamptz column forces a time-of-day and often a zone onto the value, reintroducing the ambiguity PlainDate is designed to remove. Use a DATE column and store plainDate.toString() ('2024-03-15').
How do I serialize Temporal values to JSON?
Call toString() when writing (many types also implement toJSON) and use a reviver that calls the matching Temporal.*.from() when reading. A plain JSON.parse leaves them as strings, so a reviver is what restores the typed values.