Skip to content

[typescript-fetch] Add Temporal support - #24714

Open
ondrakucera wants to merge 5 commits into
OpenAPITools:masterfrom
ondrakucera:typescript-fetch-temporal
Open

[typescript-fetch] Add Temporal support#24714
ondrakucera wants to merge 5 commits into
OpenAPITools:masterfrom
ondrakucera:typescript-fetch-temporal

Conversation

@ondrakucera

@ondrakucera ondrakucera commented Aug 15, 2026

Copy link
Copy Markdown

This adds a new configuration option to the typescript-fetch generator: "temporal" (boolean). If it is set to true, the generator uses Temporal.Instant for "type: string, format: date-time" and Temporal.PlainDate for "type: string, format: date".

This is my very first contribution here, so please let me know about anything I'm missing.

It does what the commit comment says: it adds the option to use Temporal types instead of Date for typescript-fetch. I've manually tried it on an OpenAPI YAML file having date and date-time parameters/attributes in a URL path, in URL query parameters, and in request/response (JSON) bodies. Everything seems to work well.

Rationale for the changes:

  • JavaScript's Date is suitable to be used as the data type for OpenAPI's "date-time". However, it is notoriously unpleasant to work with in applications needing to work with time-related values across different timezones (among other things). It's also the reason why projects like https://momentjs.com/ gained so much popularity in the past. The new JavaScript Temporal API tries to solve all those Date's pains.
  • Currently, typescript-fetch uses JavaScript's Date even for OpenAPI's "date" (i.e. for a date without time or timezone). The workaround is to create a Date instance at zero hours at Zulu timezone. Again, it's very easy to make a mistake when working with it. Temporal API has PlainDate, which corresponds to OpenAPI's "date" exactly.

I haven't found many existing unit tests regarding dates for typescript-fetch; in fact, I've found just a single one. So I duplicated it for the Temporal variant. I don't feel confident to create new tests covering other situations (both for Date and for Temporal).

Tagging: @TiFu @taxpon @sebastianhaas @kenisteward @Vrolijkx @macjohnny @topce @akehir @petejohansonxo @amakhrov @davidgamero @mkusaka @joscha @KannaKim.

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Adds optional Temporal support to typescript-fetch. Previously date/date-time fields were generated as Date; with temporal: true, date-time maps to Temporal.Instant and date maps to Temporal.PlainDate, with matching serialization and querystring handling. Default behavior is unchanged.

  • Detects the temporal option as a boolean and gates type mapping and template branches accordingly; Temporal.Instant/Temporal.PlainDate are registered as primitives.

  • Path, query, and form params stringify Temporal via .toString() and Date via .toISOString() (date uses .substring(0,10)), including arrays and oneOf.

  • Runtime querystring encodes Temporal.Instant/Temporal.PlainDate via .toString().

  • Models and oneOf parse Temporal via Temporal.Instant.from(...)/Temporal.PlainDate.from(...) and serialize with .toString(), with null-guards; tests added for required Temporal fields.

  • No changes for existing users unless temporal is enabled.

  • If enabling temporal, ensure the runtime provides Temporal or include a polyfill.

Written for commit 0686a0e. Summary will update on new commits.

Review in cubic

This adds a new configuration option to the typescript-fetch generator:
"temporal" (boolean). If it is set to true, the generator uses
Temporal.Instant for "type: string, format: date-time" and
Temporal.PlainDate for "type: string, format: date".
By running `./bin/utils/export_docs_generators.sh`.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/typescript-fetch/apisAssignQueryParam.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/typescript-fetch/apisAssignQueryParam.mustache:7">
P2: When a caller supplies a non-ISO-calendar `Temporal.PlainDate`, `.toString()` produces a value outside OpenAPI `format: date`. Serialize with `.toString({ calendarName: 'never' })` for the query wire format.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache:101">
P1: When consumers provide Temporal through the usual polyfill module import without assigning `globalThis.Temporal`, deserializing a date field throws `ReferenceError: Temporal is not defined`. Generate an explicit Temporal import or a documented, generated global binding.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache:101">
P1: For array properties with date or date-time items, this scalar-only branch is skipped and `FromJSON` returns strings despite the generated `Temporal.*[]` type. Map each array item through `Temporal.PlainDate.from` or `Temporal.Instant.from`, and serialize each item with `toString()`.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache:74">
P2: When a one-of contains an array of nullable date/date-time items, a JSON `null` makes the temporal map throw and returns `{}` instead of preserving the null element. Serialization has the same problem because `every(item => item instanceof Temporal...)` rejects nulls. Preserve null elements in both the conversion map and the `every` predicate.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

{{^isArray}}
{{#isDateType}}
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}{{#temporal}}Temporal.PlainDate.from(json['{{baseName}}']){{/temporal}}{{^temporal}}new Date(json['{{baseName}}']){{/temporal}}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: For array properties with date or date-time items, this scalar-only branch is skipped and FromJSON returns strings despite the generated Temporal.*[] type. Map each array item through Temporal.PlainDate.from or Temporal.Instant.from, and serialize each item with toString().

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache, line 101:

<comment>For array properties with date or date-time items, this scalar-only branch is skipped and `FromJSON` returns strings despite the generated `Temporal.*[]` type. Map each array item through `Temporal.PlainDate.from` or `Temporal.Instant.from`, and serialize each item with `toString()`.</comment>

<file context>
@@ -98,10 +98,10 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
         {{^isArray}}
         {{#isDateType}}
-        '{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
+        '{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}{{#temporal}}Temporal.PlainDate.from(json['{{baseName}}']){{/temporal}}{{^temporal}}new Date(json['{{baseName}}']){{/temporal}}),
         {{/isDateType}}
         {{#isDateTimeType}}
</file context>

{{^isArray}}
{{#isDateType}}
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
'{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}{{#temporal}}Temporal.PlainDate.from(json['{{baseName}}']){{/temporal}}{{^temporal}}new Date(json['{{baseName}}']){{/temporal}}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When consumers provide Temporal through the usual polyfill module import without assigning globalThis.Temporal, deserializing a date field throws ReferenceError: Temporal is not defined. Generate an explicit Temporal import or a documented, generated global binding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache, line 101:

<comment>When consumers provide Temporal through the usual polyfill module import without assigning `globalThis.Temporal`, deserializing a date field throws `ReferenceError: Temporal is not defined`. Generate an explicit Temporal import or a documented, generated global binding.</comment>

<file context>
@@ -98,10 +98,10 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
         {{^isArray}}
         {{#isDateType}}
-        '{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}new Date(json['{{baseName}}'])),
+        '{{name}}': {{^required}}{{#isNullable}}json['{{baseName}}'] === undefined ? undefined : json['{{baseName}}'] === null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? undefined : {{/isNullable}}{{/required}}({{#required}}{{#isNullable}}json['{{baseName}}'] == null ? null : {{/isNullable}}{{^isNullable}}json['{{baseName}}'] == null ? json['{{baseName}}'] : {{/isNullable}}{{/required}}{{#temporal}}Temporal.PlainDate.from(json['{{baseName}}']){{/temporal}}{{^temporal}}new Date(json['{{baseName}}']){{/temporal}}),
         {{/isDateType}}
         {{#isDateTimeType}}
</file context>

{{^isDateTimeType}}
{{#isDateType}}
queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString().substring(0,10);
queryParameters['{{baseName}}'] = {{#temporal}}(requestParameters['{{paramName}}'] as any).toString(){{/temporal}}{{^temporal}}(requestParameters['{{paramName}}'] as any).toISOString().substring(0,10){{/temporal}};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a caller supplies a non-ISO-calendar Temporal.PlainDate, .toString() produces a value outside OpenAPI format: date. Serialize with .toString({ calendarName: 'never' }) for the query wire format.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-fetch/apisAssignQueryParam.mustache, line 7:

<comment>When a caller supplies a non-ISO-calendar `Temporal.PlainDate`, `.toString()` produces a value outside OpenAPI `format: date`. Serialize with `.toString({ calendarName: 'never' })` for the query wire format.</comment>

<file context>
@@ -1,10 +1,10 @@
 {{^isDateTimeType}}
 {{#isDateType}}
-            queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString().substring(0,10);
+            queryParameters['{{baseName}}'] = {{#temporal}}(requestParameters['{{paramName}}'] as any).toString(){{/temporal}}{{^temporal}}(requestParameters['{{paramName}}'] as any).toISOString().substring(0,10){{/temporal}};
 {{/isDateType}}
 {{^isDateType}}
</file context>
Suggested change
queryParameters['{{baseName}}'] = {{#temporal}}(requestParameters['{{paramName}}'] as any).toString(){{/temporal}}{{^temporal}}(requestParameters['{{paramName}}'] as any).toISOString().substring(0,10){{/temporal}};
queryParameters['{{baseName}}'] = {{#temporal}}(requestParameters['{{paramName}}'] as any).toString({ calendarName: 'never' }){{/temporal}}{{^temporal}}(requestParameters['{{paramName}}'] as any).toISOString().substring(0,10){{/temporal}};

if (Array.isArray(json)) {
{{#temporal}}
try {
return json.map(value => Temporal.PlainDate.from(value));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a one-of contains an array of nullable date/date-time items, a JSON null makes the temporal map throw and returns {} instead of preserving the null element. Serialization has the same problem because every(item => item instanceof Temporal...) rejects nulls. Preserve null elements in both the conversion map and the every predicate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-fetch/modelOneOf.mustache, line 74:

<comment>When a one-of contains an array of nullable date/date-time items, a JSON `null` makes the temporal map throw and returns `{}` instead of preserving the null element. Serialization has the same problem because `every(item => item instanceof Temporal...)` rejects nulls. Preserve null elements in both the conversion map and the `every` predicate.</comment>

<file context>
@@ -69,16 +69,30 @@ export function {{classname}}FromJSONTyped(json: any, ignoreDiscriminator: boole
     if (Array.isArray(json)) {
+    {{#temporal}}
+        try {
+            return json.map(value => Temporal.PlainDate.from(value));
+        } catch {}
+    {{/temporal}}
</file context>

"Object",
"Array",
"ReadonlyArray",
"Temporal.Instant",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I see the changes to all other TypeScript markdown files, perhaps I should add this only somewhere within TypeScriptFetchClientCodegen's constructor?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to solve this in 0870de1 but unfortunately it doesn't seem to help.

{{! Assign query parameters based on their type }}
{{#isDateTimeType}}
queryParameters['{{baseName}}'] = (requestParameters['{{paramName}}'] as any).toISOString();
queryParameters['{{baseName}}'] = {{#temporal}}(requestParameters['{{paramName}}'] as any).toString(){{/temporal}}{{^temporal}}(requestParameters['{{paramName}}'] as any).toISOString(){{/temporal}};

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth noting that Temporal.Instant's toString() method gives something like this (based on my experiments):

  • Node: "2026-08-15T14:25:49.161593018Z"
  • Firefox: "2026-08-15T14:25:46.876Z"
  • Chromium: "2026-08-15T14:26:24.1674Z"

So in Node, the precision is up to nanoseconds. Also, the specification says that the number of places after the decimal point may differ because trailing zeroes are removed. We could also fix the number of fractional seconds (the method has a parameter for that).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 12 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/generators/typescript-fetch.md">

<violation number="1" location="docs/generators/typescript-fetch.md:46">
P2: The temporal option description does not mention that generated code emits bare `Temporal.Instant`/`Temporal.PlainDate` references with no import, so consumers enabling `temporal: true` must provide a Temporal runtime/polyfill plus typings or the generated code will not compile. Add the runtime/import requirement to the option documentation (via the CliOption comment it is generated from) so users know what to install.</violation>
</file>

<file name="docs/generators/typescript.md">

<violation number="1" location="docs/generators/typescript.md:100">
P2: This doc covers the base `typescript` generator (TypeScriptClientCodegen), which has no `temporal` config option — the option is only registered on TypeScriptFetchClientCodegen (`new CliOption(TEMPORAL, ...)`). Because `Temporal.Instant`/`Temporal.PlainDate` were added unconditionally to `AbstractTypeScriptClientCodegen.languageSpecificPrimitives` (the shared base of every TS generator), these entries now appear in `typescript.md` even though generated code from this generator always maps date/date-time to `Date` and never emits Temporal types. That is misleading, and it also leaked into all other TS generator docs (angular, axios, node, rxjs). Temporal types should be added to `languageSpecificPrimitives` only in the typescript-fetch codegen (e.g. conditionally when the temporal option is set), so docs of generators that don't support Temporal don't list them. Note also that `Temporal.Instant`/`Temporal.PlainDate` are not TypeScript language primitives at all — they require a Temporal runtime/polyfill, so unconditionally listing them as primitives overstates availability.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|stringEnums|Generate string enums instead of objects for enum values.| |false|
|supportsES6|Generate code that conforms to ES6.| |false|
|temporal|Setting this property to true will use Temporal data types instead of Date.| |false|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The temporal option description does not mention that generated code emits bare Temporal.Instant/Temporal.PlainDate references with no import, so consumers enabling temporal: true must provide a Temporal runtime/polyfill plus typings or the generated code will not compile. Add the runtime/import requirement to the option documentation (via the CliOption comment it is generated from) so users know what to install.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/generators/typescript-fetch.md, line 46:

<comment>The temporal option description does not mention that generated code emits bare `Temporal.Instant`/`Temporal.PlainDate` references with no import, so consumers enabling `temporal: true` must provide a Temporal runtime/polyfill plus typings or the generated code will not compile. Add the runtime/import requirement to the option documentation (via the CliOption comment it is generated from) so users know what to install.</comment>

<file context>
@@ -43,6 +43,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
 |stringEnums|Generate string enums instead of objects for enum values.| |false|
 |supportsES6|Generate code that conforms to ES6.| |false|
+|temporal|Setting this property to true will use Temporal data types instead of Date.| |false|
 |useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |true|
 |useSquareBracketsInArrayNames|Setting this property to true will add brackets to array attribute names, e.g. my_values[].| |false|
</file context>
Suggested change
|temporal|Setting this property to true will use Temporal data types instead of Date.| |false|
|temporal|Setting this property to true will use Temporal data types instead of Date. Requires a Temporal runtime/polyfill (e.g. Temporal.Instant, Temporal.PlainDate) to be available at compile/runtime.| |false|

<li>ReturnType</li>
<li>Set</li>
<li>String</li>
<li>Temporal.Instant</li>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This doc covers the base typescript generator (TypeScriptClientCodegen), which has no temporal config option — the option is only registered on TypeScriptFetchClientCodegen (new CliOption(TEMPORAL, ...)). Because Temporal.Instant/Temporal.PlainDate were added unconditionally to AbstractTypeScriptClientCodegen.languageSpecificPrimitives (the shared base of every TS generator), these entries now appear in typescript.md even though generated code from this generator always maps date/date-time to Date and never emits Temporal types. That is misleading, and it also leaked into all other TS generator docs (angular, axios, node, rxjs). Temporal types should be added to languageSpecificPrimitives only in the typescript-fetch codegen (e.g. conditionally when the temporal option is set), so docs of generators that don't support Temporal don't list them. Note also that Temporal.Instant/Temporal.PlainDate are not TypeScript language primitives at all — they require a Temporal runtime/polyfill, so unconditionally listing them as primitives overstates availability.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/generators/typescript.md, line 100:

<comment>This doc covers the base `typescript` generator (TypeScriptClientCodegen), which has no `temporal` config option — the option is only registered on TypeScriptFetchClientCodegen (`new CliOption(TEMPORAL, ...)`). Because `Temporal.Instant`/`Temporal.PlainDate` were added unconditionally to `AbstractTypeScriptClientCodegen.languageSpecificPrimitives` (the shared base of every TS generator), these entries now appear in `typescript.md` even though generated code from this generator always maps date/date-time to `Date` and never emits Temporal types. That is misleading, and it also leaked into all other TS generator docs (angular, axios, node, rxjs). Temporal types should be added to `languageSpecificPrimitives` only in the typescript-fetch codegen (e.g. conditionally when the temporal option is set), so docs of generators that don't support Temporal don't list them. Note also that `Temporal.Instant`/`Temporal.PlainDate` are not TypeScript language primitives at all — they require a Temporal runtime/polyfill, so unconditionally listing them as primitives overstates availability.</comment>

<file context>
@@ -97,6 +97,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
 <li>ReturnType</li>
 <li>Set</li>
 <li>String</li>
+<li>Temporal.Instant</li>
+<li>Temporal.PlainDate</li>
 <li>ThisParameterType</li>
</file context>

@macjohnny

Copy link
Copy Markdown
Member

FYI there is a similar effort by @b2l in #24637, could you please coordinate how to consolidate?

@Mattias-Sehlstedt

Mattias-Sehlstedt commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Hi,

Could we provide some additional background for this feature introduction (edit the description to add it). E.g., is it that we want to better mirror actual type date and date-time, or is it that it handles precision differently? Or is it that generated models offer better ways to handle values when sending/receiving them from the client?

Given that we do not seem super certain that this does exactly what we want regarding features (raising that it "seems to work fine"), would it be an idea to test this locally in your project for a while before submitting it as a feature to the project? It should be possible since almost all changes seems to rely on mustache changes, which can be overridden in your local project.

@ondrakucera

Copy link
Copy Markdown
Author

FYI there is a similar effort by @b2l in #24637, could you please coordinate how to consolidate?

Interesting, I didn't see that one (I tried looking specifically for Temporal-related issues/PRs). Yes, our efforts have some overlaps, although the goal is slightly different:

  • [typescript-fetch] centralise date handling, add dateLibrary, fix for… #24637 tries to change how OpenAPI's date and date-time are currently handled by JavaScript's Date (while also adding the option of keeping attributes as strings).
  • My effort was to not change the current behavior at all, I added the option to use Temporal API as an alternative, with the intention to mimic the current Date-based behavior as closely as possible. So no internal changes, the same behavior, only around the newer JavaScript API.

I believe that in the end, it would be nice to have both, so now it's more or less a matter of the order to merge these. Either #24637 can be merged first and then I can try to redo my work as a third library type, or mine can be merged first because I believe it's easier to review.

Obviously, I'd like to see Temporal support in the typescript-fetch generator soon, so for me it depends on how quickly #24637 can be merged. If it's likely to be soon, I'm happy to wait with my PR for that and then redo my work on top of that.

@ondrakucera

ondrakucera commented Aug 15, 2026

Copy link
Copy Markdown
Author

Hi,

Could we provide some additional background for this feature introduction (edit the description to add it). E.g., is it that we want to better mirror actual type date and date-time, or is it that it handles precision differently? Or is it that generated models offer better ways to handle values when sending/receiving them from the client?

Given that we do not seem super certain that this does exactly what we want regarding features (raising that it "seems to work fine"), would it be an idea to test this locally in your project for a while before submitting it as a feature to the project? It should be possible since almost all changes seems to rely on mustache changes, which can be overridden in your local project.

Oh, absolutely. I assumed that the pain that is JavaScript's Date is shared world-wide. :-) Date, apart having quite a weird constructor, represents a point in time, which makes it suitable for OpenAPI's "date-time". However, getters of Date instances always return values (e.g. day, month, year) in the runtime environment timezone (e.g. the browser's timezone). When working with points in time in an application, which needs to show times from different time zones (then the user's) or calculating time-related values (e.g. "add five dates, three hours, and two minutes to this Date instance"), it leads to a messy and error-prone code.

Which is why there is a shiny, brand-new JavaScript API for working with such values, called Temporal. It's specifically designed to help with aforementioned problems. It is currently supported by Firefox, everything Chromium-based, Node, and Deno. Safari doesn't support it yet but Apple is working on it (and I think they already have some kind of a preview). Also, a polyfill can be used in environments without native support.

When we look at OpenAPI's "date", the situation is even clearer: JavaScript's Date has no support for a date without time. So currently, when using the typescript-fetch generator, an instance of Date at zero hours at Zulu timezone is used as the next best thing. However, this instance can mean different things (when using getters for days, hours, etc.) in different browser timezones (as described in #24637). Here, Temporal's PlainDate comes to the rescue because it represents exactly OpenAPI's "date": a date without time (and timezone).

As for testing it on a real project: I can suggest it, the problem is that the project uses it via Maven (pointed at the general public Maven artifactory), which I can't really do anything about, so unless there's a published JAR of a version with my changes, I can't really use it. :-(

@Mattias-Sehlstedt

Copy link
Copy Markdown
Contributor

Thanks for the elaboration.

Does your Maven approach not work with https://openapi-generator.tech/docs/customization#user-defined-templates? I.e., you can in the command line point to a mustache file that is to replace the one that the project has. So you could define the templates in this PR (with minor modification) locally and then point to them, and then the code generator should use those.

@ondrakucera

Copy link
Copy Markdown
Author

Thanks for the elaboration.

Does your Maven approach not work with https://openapi-generator.tech/docs/customization#user-defined-templates? I.e., you can in the command line point to a mustache file that is to replace the one that the project has. So you could define the templates in this PR (with minor modification) locally and then point to them, and then the code generator should use those.

I see. Honestly, it's the first time I've even ever seen a Mustage template, so I haven't really thought about this possibility. I assume it would be possible but only to a certain point. If you look at my changes, they actually do everything conditionally, based on a new "additional property" for the generator. The only way I could just point my project to a different sets of templates would be to make my changes unconditionally, because the generator would never pass that new additional property to Mustage as a parameter (because the generator in the current version doesn't have that property).

So, I could perhaps do this but it wouldn't be exactly the right test because I would actually be using different templates than I am proposing in this PR.

@Mattias-Sehlstedt

Copy link
Copy Markdown
Contributor

What we would like to test is the behavior of Temporal in different contexts, so changing from Date unconditionally. So switching

{{#temporal}}
if (requestParameters['{{paramName}}'] instanceof Temporal.Instant) {
    urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(requestParameters['{{paramName}}'].toString()));
{{/temporal}}
{{^temporal}}
if (requestParameters['{{paramName}}'] instanceof Date) {
    urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(requestParameters['{{paramName}}'].toISOString()));
{{/temporal}}
}

to

if (requestParameters['{{paramName}}'] instanceof Temporal.Instant) {
    urlPath = urlPath.replace({{=<< >>=}}'{<<baseName>>}'<<={{ }}=>>, encodeURIComponent(requestParameters['{{paramName}}'].toString()));
}

is basically exactly what we are after. This approach would also allow you to adjust it instantly without waiting for reviews and releases if we find that there are slightly better ways of doing it. The fixture for being able to switch between implementations is trivial, and it is only (de)serialization behavior we are interested in testing.

This approach would also allow more time for those interested in transitioning to Temporal to review the changes, so that we can avoid a situation when your implementation is merged and then someone comes and argues that it should actually be done slightly differently (this can of course be "solved" with more configuration settings, but generally one would like to avoid that). The PR shared earlier also makes it much easier and intuitive to offer further date customization, which is great.

@ondrakucera

Copy link
Copy Markdown
Author

What we would like to test is the behavior of Temporal in different contexts, so changing from Date unconditionally. ...

Alright, I understand. I'll try to discuss it with our team. We'll first need to do a slightly larger refactoring (exactly because of issues with Dates) and based on our current schedule, it'll take a few weeks (the nearest sprints are unfortunately already full). But then I can attempt switching to these modified templates within our codebase and see how it goes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants