From dee7fc134e92e6d1ad4fb3a429cc7bf59e72498d Mon Sep 17 00:00:00 2001 From: Michael Nahkies Date: Mon, 18 May 2026 18:46:32 +0100 Subject: [PATCH] feat: support format: date / time --- .../api.github.com.yaml/generated.ts | 2 +- .../generated/api.github.com.yaml/schemas.ts | 6 +++--- .../api.github.com.yaml/generated.ts | 2 +- .../generated/api.github.com.yaml/schemas.ts | 6 +++--- .../zod-v4-schema-builder.integration.spec.ts | 2 +- .../schema-builders/zod-v4-schema-builder.ts | 12 +++++------ .../zod-v4-schema-builder.unit.spec.ts | 20 +++++++++++++++++++ 7 files changed, 35 insertions(+), 15 deletions(-) diff --git a/integration-tests/typescript-express/src/generated/api.github.com.yaml/generated.ts b/integration-tests/typescript-express/src/generated/api.github.com.yaml/generated.ts index 717bbe68..b50df28e 100644 --- a/integration-tests/typescript-express/src/generated/api.github.com.yaml/generated.ts +++ b/integration-tests/typescript-express/src/generated/api.github.com.yaml/generated.ts @@ -87698,7 +87698,7 @@ export function createRouter( const metaGetAllVersionsResponseBodyValidator = responseValidationFactory( [ - ["200", z.array(z.string())], + ["200", z.array(z.iso.date())], ["404", s_basic_error], ], undefined, diff --git a/integration-tests/typescript-express/src/generated/api.github.com.yaml/schemas.ts b/integration-tests/typescript-express/src/generated/api.github.com.yaml/schemas.ts index 63b1aa74..d46d5556 100644 --- a/integration-tests/typescript-express/src/generated/api.github.com.yaml/schemas.ts +++ b/integration-tests/typescript-express/src/generated/api.github.com.yaml/schemas.ts @@ -2543,7 +2543,7 @@ export const s_pages_https_certificate = z.object({ ]), description: z.string(), domains: z.array(z.string()), - expires_at: z.string().optional(), + expires_at: z.iso.date().optional(), }) export const s_pages_source_hash = z.object({ @@ -4011,7 +4011,7 @@ export const s_copilot_organization_details = z.intersection( export const s_copilot_usage_metrics_day = z.intersection( z.object({ - date: z.string(), + date: z.iso.date(), total_active_users: z.coerce.number().optional(), total_engaged_users: z.coerce.number().optional(), copilot_ide_code_completions: s_copilot_ide_code_completions.optional(), @@ -6485,7 +6485,7 @@ export const s_copilot_seat_details = z.object({ assignee: s_nullable_simple_user.optional(), organization: s_nullable_organization_simple.optional(), assigning_team: z.union([s_team, s_enterprise_team]).nullable().optional(), - pending_cancellation_date: z.string().nullable().optional(), + pending_cancellation_date: z.iso.date().nullable().optional(), last_activity_at: z.iso.datetime({offset: true}).nullable().optional(), last_activity_editor: z.string().nullable().optional(), created_at: z.iso.datetime({offset: true}), diff --git a/integration-tests/typescript-koa/src/generated/api.github.com.yaml/generated.ts b/integration-tests/typescript-koa/src/generated/api.github.com.yaml/generated.ts index fd24b717..88a2d447 100644 --- a/integration-tests/typescript-koa/src/generated/api.github.com.yaml/generated.ts +++ b/integration-tests/typescript-koa/src/generated/api.github.com.yaml/generated.ts @@ -84767,7 +84767,7 @@ export function createRouter( const metaGetAllVersionsResponseValidator = responseValidationFactory( [ - ["200", z.array(z.string())], + ["200", z.array(z.iso.date())], ["404", s_basic_error], ], undefined, diff --git a/integration-tests/typescript-koa/src/generated/api.github.com.yaml/schemas.ts b/integration-tests/typescript-koa/src/generated/api.github.com.yaml/schemas.ts index 63b1aa74..d46d5556 100644 --- a/integration-tests/typescript-koa/src/generated/api.github.com.yaml/schemas.ts +++ b/integration-tests/typescript-koa/src/generated/api.github.com.yaml/schemas.ts @@ -2543,7 +2543,7 @@ export const s_pages_https_certificate = z.object({ ]), description: z.string(), domains: z.array(z.string()), - expires_at: z.string().optional(), + expires_at: z.iso.date().optional(), }) export const s_pages_source_hash = z.object({ @@ -4011,7 +4011,7 @@ export const s_copilot_organization_details = z.intersection( export const s_copilot_usage_metrics_day = z.intersection( z.object({ - date: z.string(), + date: z.iso.date(), total_active_users: z.coerce.number().optional(), total_engaged_users: z.coerce.number().optional(), copilot_ide_code_completions: s_copilot_ide_code_completions.optional(), @@ -6485,7 +6485,7 @@ export const s_copilot_seat_details = z.object({ assignee: s_nullable_simple_user.optional(), organization: s_nullable_organization_simple.optional(), assigning_team: z.union([s_team, s_enterprise_team]).nullable().optional(), - pending_cancellation_date: z.string().nullable().optional(), + pending_cancellation_date: z.iso.date().nullable().optional(), last_activity_at: z.iso.datetime({offset: true}).nullable().optional(), last_activity_editor: z.string().nullable().optional(), created_at: z.iso.datetime({offset: true}), diff --git a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.integration.spec.ts b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.integration.spec.ts index e62fe64e..6385114b 100644 --- a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.integration.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.integration.spec.ts @@ -43,7 +43,7 @@ describe.each( export const s_SimpleObject = z.object({ str: z.string(), num: z.coerce.number(), - date: z.string(), + date: z.iso.date(), datetime: z.iso.datetime({ offset: true }), optional_str: z.string().optional(), required_nullable: z.string().nullable(), diff --git a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.ts b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.ts index 295fc9b1..73631aa6 100644 --- a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.ts +++ b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.ts @@ -329,13 +329,13 @@ export class ZodV4Builder extends AbstractSchemaBuilder< ) } - // todo: add support for date, time const base = - model.format === "date-time" - ? "iso.datetime({offset:true})" - : model.format === "email" - ? "email()" - : "string()" + [ + ["date-time", "iso.datetime({offset:true})"], + ["date", "iso.date()"], + ["time", "iso.time()"], + ["email", "email()"], + ].find((it) => it[0] === model.format)?.[1] ?? "string()" return [ zod, diff --git a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.unit.spec.ts b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.unit.spec.ts index 636000c3..5afa7bd4 100644 --- a/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.unit.spec.ts +++ b/packages/openapi-code-generator/src/typescript/common/schema-builders/zod-v4-schema-builder.unit.spec.ts @@ -554,6 +554,7 @@ describe("typescript/common/schema-builders/zod-v4-schema-builder - unit tests", ) await expect(execute("some string")).rejects.toThrow("Invalid email") }) + it("supports date-time", async () => { const {code, execute} = await getActual( ir.string({format: "date-time"}), @@ -570,6 +571,25 @@ describe("typescript/common/schema-builders/zod-v4-schema-builder - unit tests", "Invalid ISO datetime", ) }) + + it("supports date", async () => { + const {code, execute} = await getActual(ir.string({format: "date"})) + + expect(code).toMatchInlineSnapshot('"const x = z.iso.date()"') + + await expect(execute("2024-05-25")).resolves.toBe("2024-05-25") + await expect(execute("some string")).rejects.toThrow("Invalid ISO date") + }) + + it("supports time", async () => { + const {code, execute} = await getActual(ir.string({format: "time"})) + + expect(code).toMatchInlineSnapshot('"const x = z.iso.time()"') + + await expect(execute("08:20:00")).resolves.toBe("08:20:00") + await expect(execute("some string")).rejects.toThrow("Invalid ISO time") + }) + it("supports binary", async () => { const {code} = await getActual(ir.string({format: "binary"}))