OpenAPITools / OpenAPITools/openapi-generator
[BUG][typescript-fetch] `format: date` handling is inconsistent and shifts by a day west of UTC
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
[BUG][typescript-fetch] format: date handling is inconsistent and shifts by a day west of UTC
Bug Report Checklist
- Have you provided a full/minimal spec to reproduce the issue?
- Have you validated the input using an OpenAPI validator (example)?
- Have you tested with the latest master to confirm the issue still exists?
- Have you searched for related issues/PRs?
- What's the actual output vs expected output?
- [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
typescript-fetch inlines its date (de)serialization separately in four templates
(modelGeneric.mustache, modelOneOf.mustache, apis.mustache,
apisAssignQueryParam.mustache) and in runtime.mustache's querystring. Because
each site spells the conversion out by hand, they have drifted apart, which produces
three distinct problems.
1. format: date is not handled at all for form parameters
apis.mustache has an isDateTimeType branch for form params but no isDateType
branch, so a format: date parameter falls through to the primitive branch and the
Date is appended to FormData unconverted:
formParams.append('startsOn', requestParameters['startsOn'] as any);
The browser stringifies it with Date.prototype.toString(), so the request body
carries startsOn=Wed+Aug+05+2026+00%3A00%3A00+GMT%2B0200+(Central+European+Summer+Time)
instead of startsOn=2026-08-05. Every other location (path, query, model) does
convert, so this is inconsistent within the same generator.
2. A format: date round-trip loses a day west of UTC
An RFC 3339 full-date is a calendar date: no time, no offset. A JS Date is an
instant, so representing one means picking a wall clock — and parsing and serializing
must pick the same one. Currently they don't:
- parse:
new Date('2026-08-05')— a date-only string is specified to parse as UTC - serialize:
value.toISOString().substring(0, 10)— the UTC calendar day
Those two agree with each other, but not with any locally-built Date, which is what
a date picker or new Date(2026, 7, 5) produces. And getDate()-style display of a
parsed value is wrong west of UTC:
// TZ=America/New_York
const fromApi = new Date('2026-08-05'); // 2026-08-04T20:00:00-04:00
fromApi.getDate(); // 4 ← displayed as the 4th
fromApi.toISOString().substring(0, 10); // '2026-08-05' (round-trips, but…)
const fromPicker = new Date(2026, 7, 5); // 2026-08-05T00:00:00-04:00
fromPicker.toISOString().substring(0, 10);// '2026-08-04' ← sends the wrong day
So today a user either displays the wrong day or sends the wrong day, depending on
where the Date came from. If the API says the 5th of August, the client should show
and send the 5th of August in every time zone.
3. Date-vs-string representation is decided by an unrelated flag
processOpts maps date/DateTime to Date only inside if (!withoutRuntimeChecks).
withoutRuntimeChecks is documented as being about runtime validation of payloads, yet
it silently also switches the type of every date field to string. There is no way to
ask for string dates while keeping runtime checks — which is what you want for SSR/RSC
(a Date is not serializable across the server/client boundary), or when the consumer
owns date parsing (Luxon, day.js, Temporal).
openapi-generator version
master (7.25.0-SNAPSHOT). Present in every 7.x release; the form-parameter gap and the
UTC/local asymmetry are long-standing.
OpenAPI declaration file
openapi: 3.0.3
info: { title: Date handling, version: 1.0.0 }
paths:
/events/{onDate}:
get:
operationId: listEvents
parameters:
- { name: onDate, in: path, required: true, schema: { type: string, format: date } }
- { name: from, in: query, schema: { type: string, format: date } }
responses: { '200': { description: ok } }
/events:
post:
operationId: createEvent
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [startsOn]
properties:
startsOn: { type: string, format: date }
createdAt: { type: string, format: date-time }
responses: { '200': { description: ok } }
Generation details
openapi-generator-cli generate -g typescript-fetch -i date-handling.yaml -o out
Steps to reproduce
- Generate with the spec above.
grep -n "formParams.append('startsOn'" out/apis/DefaultApi.ts— no conversion (problem 1).- In
TZ=America/New_York, runEventToJSON({ startsOn: new Date(2026, 7, 5) })— get
2026-08-04(problem 2).
Related issues/PRs
Same root cause, previously reported for individual call sites:
- #9805, #12134 (date shifted by one day)
- #7651 (date/date-time representation)
Suggest a fix
Two parts, in one PR: #24637
Centralise. Emit one helper set in runtime.ts — serializeDate,
serializeDateTime, parseDate, parseDateTime — and route every call site through
it, so the representation is defined once and cannot drift again. That also closes the
missing form-parameter branch.
Make the semantics symmetric. format: date uses the local calendar on both ends
(getFullYear/getMonth/getDate out, new Date(y, m - 1, d) in), so a stated day
round-trips and displays as that day in every time zone. format: date-time is a
genuine instant and stays toISOString().
Make the representation an explicit option. A new dateLibrary flag, matching the
naming already used by the java/kotlin/dart generators:
| value | behaviour |
|---|---|
date (default) |
native Date, converted by the runtime — today's behaviour, minus the bugs |
string |
pass values through untouched, consumer owns date handling |
withoutRuntimeChecks: true keeps implying string, since with no model code there is
nothing to convert with; passing dateLibrary: date alongside it warns and falls back.
The default is unchanged, so this is not a breaking change for anyone whose dates were
already correct.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the five TypeScript-fetch templates named in the issue and runtime.mustache, then generate the supplied date-handling.yaml with openapi-generator-cli. Trace the existing date and date-time conversion paths, including form parameters and processOpts. Done means the generated client handles date values consistently across model, path, query, and form usage, with the documented dateLibrary behavior and no regression in date-time handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- openapi, typescript
- Domain
- devtools, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100