Temporal Utils by Ian Macalinao
    Preparing search index...

    Module temporal-zod

    temporal-zod

    NPM version

    Zod validators for Temporal types.

    This depends on the temporal-polyfill package.

    This library exports two Zod validators for each Temporal type: one with type coercion and one without.

    Strings are coerced to the appropriate Temporal type, and for the Instant type, Date objects are also coerced to Instant objects.

    • zPlainDate/zPlainDateInstance - A Zod validator for the PlainDate type.
    • zPlainTime/zPlainTimeInstance - A Zod validator for the PlainTime type.
    • zPlainDateTime/zPlainDateTimeInstance - A Zod validator for the PlainDateTime type.
    • zPlainYearMonth/zPlainYearMonthInstance - A Zod validator for the PlainYearMonth type.
    • zPlainMonthDay/zPlainMonthDayInstance - A Zod validator for the PlainMonthDay type.
    • zDuration/zDurationInstance - A Zod validator for the Duration type.
    • zInstant/zInstantInstance - A Zod validator for the Instant type. This also coerces Date objects to Instant objects.
    • zZonedDateTime/zZonedDateTimeInstance - A Zod validator for the ZonedDateTime type.
    import * as z from "zod";
    import { zZonedDateTime } from "temporal-zod";

    const schema = z.object({
    zonedDateTime: zZonedDateTime,
    });

    const input = {
    zonedDateTime: "2023-05-15T13:45:30+08:00[Asia/Manila]",
    };

    const result = schema.parse(input);
    // result.zonedDateTime is a ZonedDateTime object

    You may view the tests for more examples.

    The default temporal-zod export registers JSON Schema metadata on every validator via Zod's .meta(), so z.toJSONSchema() works out of the box:

    import * as z from "zod";
    import { zPlainDate, zInstant } from "temporal-zod";

    const schema = z.object({
    date: zPlainDate,
    instant: zInstant,
    });

    const jsonSchema = z.toJSONSchema(schema);
    // Produces a JSON Schema with $defs for Temporal.PlainDate, Temporal.Instant,
    // including type, description, pattern, and format where applicable.

    If you don't need JSON Schema support, you can import from temporal-zod/base for a smaller bundle. This gives you the same validators without the JSON Schema metadata registration side effect:

    import { zPlainDate, zInstant } from "temporal-zod/base";
    

    This is backwards-compatible with the pre-JSON Schema versions of temporal-zod.

    Every validator this package exports is registered in temporalRegistry, a Zod registry scoped to temporal-zod, mapped to the JSON Schema it converts to. Use it to recognize a Temporal validator inside your own schema walk — by identity, rather than by guessing from z.globalRegistry metadata, which cannot tell our schemas apart from your own:

    import { temporalRegistry, zInstant } from "temporal-zod";

    temporalRegistry.has(zInstant); // true
    temporalRegistry.get(zInstant); // { type: "string", format: "date-time", … }

    The metadata-free temporal-zod/base validators are not members, since they carry no JSON Schema.

    Values travel as the plain ISO strings toJSON() produces, and the validator on the receiving end revives them. The published pattern accepts everything toJSON() can emit, including the [u-ca=…] annotation added under a non-ISO calendar, the full reference-date form PlainYearMonth and PlainMonthDay take under such a calendar, and signed six-digit years.

    One boundary is worth knowing: PlainDate also advertises format: "date", which is RFC 3339 full-date and cannot carry an annotation. A validator that asserts format will therefore reject a non-ISO PlainDate even though the pattern accepts it. The format is kept because it is correct and useful for the ISO case, which is the overwhelmingly common one.

    oRPC builds its OpenAPI documents with its own ZodToJsonSchemaConverter, which ignores the .meta() metadata above. Install temporal-orpc to fix that; it keeps the oRPC dependency out of temporal-zod.

    If you are using tRPC, you likely use Zod to validate your inputs and outputs. However, when using it with Tanstack Query, since the Temporal types get mapped to an object, you should ensure that you are using the instance of the Temporal type rather than the one with type coercion. Otherwise, the query cache will not work as expected.

    To do this, use the instance matcher of the Temporal type rather than the one with type coercion.

    That is:

    // wrong
    const procedure = myProcedure.input(
    z.object({
    plainDate: zPlainDate,
    }),
    );

    // correct
    const procedure = myProcedure.input(
    z.object({
    plainDate: zPlainDateInstance,
    }),
    );

    Apache-2.0

    base
    index