Repository navigation
fix(json-schema): only coerce date strings with a valid time suffix - #2212
Conversation
`DATE_TIME_PATTERN` allowed any text after `T` or a space, so V8's legacy parser read trailing words and produced a different date that validation accepted (`'2020-06-15 jan'` became 2020-01-06). The suffix is now limited to a time with optional seconds, fraction and `Z` or numeric offset, so such strings stay strings and fail validation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179UCu7YNr8SzgX4xx6MkrU
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | 5c09136 | Commit Preview URL Branch Preview URL |
Oct 12 2026, 06:22 AM |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Tightened the date coercion pattern (
packages/json-schema/src/coercer.ts:436): the time suffix is now required to beHH:MM[:SS[.fff]][Z|±HH:MM]instead of accepting arbitrary trailing text, so V8's legacy parser can no longer silently reinterpret trailing words as a different date. - Regression tests (
packages/json-schema/src/coercer.test.ts:129-139): adds a space-separated offset positive case and asserts dates followed by non-time text ('2020-06-15 jan','2020-01-01Tfoo', etc.) are left untouched.
I confirmed the new pattern is a strict subset of the old one, all existing date cases still coerce, and the only V8-parseable form newly left as a string is a lowercase z — which is not strict ISO 8601 and is rejected by zod's z.iso.datetime() and valibot's isoTimestamp(), matching the documented "ISO 8601 date and datetime strings" contract in docs/plugins/smart-coercion.mdx. packages/json-schema/src/coercer.test.ts passes (67 tests).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
|

JsonSchemaCoercerturns strings intoDates for schemas markedx-native-type: 'date'(z.date(),v.date(), arktypeDate).DATE_TIME_PATTERNallowed any text afterTor a space. The day-range check only looks at the leadingYYYY-MM-DD, so the whole string reachednew Date(). V8's legacy parser then read the trailing words and returned a different date, which the validator accepted silently:'2020-06-15 jan''2020-01-15 December''2020-06-15 12:00 jan'This breaks the coercer's safe/lossless rule, and it is a gap in the pattern tightening from #2127.
Change
The suffix is now limited to a time (
HH:MM, optional seconds and fraction), optionally followed byZor a±HH:MM/±HHMMoffset:The new pattern accepts a strict subset of what the old one did, so it can only coerce fewer strings. The day-range and NaN checks are unchanged, and it is still one regex test per string. Every documented form still coerces: date only,
T/space with a time, seconds, fractions,Z, offsets with or without a colon, and expanded years. The only correctly parsed form that now stays a string is a lowercasez('…T06:15:00z'), which zod'sz.iso.datetime()and valibot'sisoTimestamp()reject too.Tests
'2020-06-15 jan','2020-01-15 December','2020-06-15 12:00 jan','2020-01-01T06:15:00 GMT'and'2020-01-01Tfoo'are returned unchanged. It fails before the fix.'2020-01-01 06:15:00+07:00'still coerces.pnpm vitest runfor json-schema, zod, valibot, arktype andtests/plugins(266 tests),pnpm type:checkandpnpm lintall pass. In the coercer bench, the cases that run the regex were the same or slightly faster.🤖 Generated with Claude Code
https://claude.ai/code/session_0179UCu7YNr8SzgX4xx6MkrU
Generated by Claude Code