From f0be684e431fcafceb4233f22c22be59415650cf Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 7 Sep 2026 11:01:29 -0700 Subject: [PATCH 1/2] fix(typescript-effect-schema): validate date and time formats --- .../TypeScriptEffectSchemaRenderer.ts | 6 +++--- test/languages.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts b/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts index b61a3764b2..ac2d00bf12 100644 --- a/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts +++ b/packages/quicktype-core/src/language/TypeScriptEffectSchema/TypeScriptEffectSchemaRenderer.ts @@ -195,11 +195,11 @@ export class TypeScriptEffectSchemaRenderer extends ConvenienceRenderer { ? 'S.transform(S.Literal("true", "false"), S.Boolean, { strict: true, decode: (value) => value === "true", encode: (value) => value ? "true" : "false" })' : 'S.Literal("true", "false")'; if (_transformedStringType.kind === "date") - return "S.String.pipe(S.pattern(/^\\d{4}-\\d{2}-\\d{2}$/))"; + return 'S.String.pipe(S.pattern(/^\\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2]\\d|3[01])$/), S.filter(value => (new Date(value + "T00:00:00Z").toJSON() || "").slice(0, 10) === value))'; if (_transformedStringType.kind === "time") - return "S.String.pipe(S.pattern(/^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/))"; + return "S.String.pipe(S.pattern(/^(?:[01]\\d|2[0-3]):[0-5]\\d:(?:[0-5]\\d|60)(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$/i))"; if (_transformedStringType.kind === "date-time") - return "S.String.pipe(S.pattern(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})$/))"; + return 'S.String.pipe(S.pattern(/^\\d{4}-(?:0[1-9]|1[0-2])-(?:[0-2]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d:(?:[0-5]\\d|60)(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$/i), S.filter(value => (new Date(value.slice(0, 10) + "T00:00:00Z").toJSON() || "").slice(0, 10) === value.slice(0, 10)))'; if (_transformedStringType.kind === "integer-string") return coerceStrings ? "S.NumberFromString.pipe(S.int())" diff --git a/test/languages.ts b/test/languages.ts index ebfdb206ec..8a7be4ef7b 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1787,6 +1787,8 @@ export const TypeScriptEffectSchemaLanguage: Language = { "minmaxlength", "bool-string", "date-time", + "date", + "time", "integer-string", "pattern", "minmax", From 4ba516e62e5f1bcdda92209889b649afad8217ea Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 7 Sep 2026 21:46:25 -0400 Subject: [PATCH 2/2] fix(python): validate ISO date-times (#3518) --- .../quicktype-core/src/language/Python/JSONPythonRenderer.ts | 4 ++-- test/languages.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts b/packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts index 738748a087..5ecbb55b7e 100644 --- a/packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts +++ b/packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts @@ -521,7 +521,7 @@ export class JSONPythonRenderer extends PythonRenderer { this.emitLine( "assert isinstance(x, str) and ", this.withModuleImport("re"), - '.match(r"^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?$", x)', + '.match(r"^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:[Zz]|[+-]\\d{2}:\\d{2})?$", x)', ); this.emitLine("return dateutil.parser.parse(x).timetz()"); }, @@ -542,7 +542,7 @@ export class JSONPythonRenderer extends PythonRenderer { ], () => { this._haveDateutil = true; - this.emitLine("return dateutil.parser.parse(x)"); + this.emitLine("return dateutil.parser.isoparse(x)"); }, ); } diff --git a/test/languages.ts b/test/languages.ts index 8a7be4ef7b..539bcce001 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -297,6 +297,8 @@ export const PythonLanguage: Language = { "union", "no-defaults", "date-time", + "date", + "time", "integer-string", "bool-string", "uuid",