Skip to content

feat(engine): support date, time and date-time formats - #15

Merged
kozmaadrian merged 5 commits into
mainfrom
date-time-fields
Sep 4, 2026
Merged

kozmaadrian merged 5 commits into
mainfrom
date-time-fields

Conversation

@kozmaadrian

@kozmaadrian kozmaadrian commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds support for the JSON Schema format keyword — date, time, and date-time — to the state engine. String nodes carry their format through the model so consumers can render the matching date/time control, and stored values are validated for shape and calendar/clock validity.

What changed

  • Schema → node: the compiler extracts format (date | time | date-time) onto string nodes (schema.js) and carries it through the model (model.js). Only these three values are recognized; format on a non-string, an unsupported value, or alongside enum is dropped.

  • Validation: validation.js validates the value's shape and real calendar/clock validity, reported under keyword: "format" with params: { format }:

    format Accepts Rejects Message
    date YYYY-MM-DD (real date) 2026-02-30, 08/14/2026 Must be a valid date.
    time HH:MM / HH:MM:SS (24h) 24:00, 9:5 Must be a valid time.
    date-time UTC …:00Z, zero seconds offset (+02:00), non-zero seconds, missing Z Must be a valid date and time.
  • date and time are floating (no offset). date-time is an absolute instant constrained to canonical UTC — deliberately stricter than RFC 3339 (only Z, zero seconds) so there is one canonical storage form that every writer produces, comparable byte-for-byte.

  • Empty/absent values are not validated (consistent with the existing empty-value semantics).

Node shape

String nodes may now carry an optional format field (peer to enumValues):

{ kind: 'string', format?: 'date' | 'time' | 'date-time', … }

Docs

  • schema-spec.md — format added to the supported-constraints section with stored forms, the canonical UTC rule, and validation messages (plus the complete example).
  • validation-reference.md — worked date/time examples added; corrected the note that previously listed format as not checked.
  • architecture.md, model-builder.md, schema-builder.md — node-shape references updated.

Testing

  • validation.test.js — date/time/date-time cases incl. impossible dates, leap day, out-of-range time, offset/seconds rejection, and the 4-digit-year boundary.
  • schema.test.js — format captured for the three values, dropped for unsupported values and non-string types.

Compatibility

  • Additive for schemas that don't use these formats.
  • Behavioral change for schemas that already declare format: date | time | date-time: previously format was ignored (any string validated); now these values are validated. In particular, date-time values that aren't canonical UTC (offsets, non-zero seconds) will now be reported invalid.

Extract the JSON Schema string `format` (date | date-time | time) onto
string nodes and carry it through the model so the editor can pick a
date/time widget. Unsupported formats and formats on non-string types
are dropped.
date/time are floating (no offset); date-time is constrained to UTC (Z)
with zero seconds for one canonical storage form the editor and any
SDK-only writer both produce. Validates both shape and calendar/time
validity, deliberately stricter than RFC 3339.
Add the format keyword (date | time | date-time) to the schema spec —
the single source of truth for authoring agents — with stored forms,
the canonical UTC date-time rule, and validation messages. Correct the
validation reference (format was listed as not-checked) and add the
node-level format field to the builder/architecture references.

@tyge68 tyge68 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid implementation — regex shapes, calendar/leap-year math, UTC canonicalization, and empty-value handling all check out against the spec in the description, and the test coverage (leap day, 4-digit-year boundary, offset/seconds rejection) is thorough. CI green. A couple of minor, non-blocking notes inline.


// Native RFC 3339 date/time hint. Only applies to strings; drives the widget
// kind and the value-shape check in validation.js.
if (kind === 'string' && DATE_FORMATS.has(schema?.format)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Precedence note: this check sits after the x-semantic-type check above, so a string with both x-semantic-type: long-text and format: date silently drops format (semanticType wins). The PR description documents enum-beats-format but not semanticType-beats-format. Worth a doc line (or a schema.test.js case) so the precedence is explicit rather than incidental.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Flipped it so format wins now, since format is a standard JSON Schema keyword and x-semantic-type is just vendor glue that external validators ignore anyway. Documented the precedence (enum, then format, then x-semantic-type) in schema-spec.md and added a test for the combo.

Comment thread src/state-engine/validation.js Outdated

// Wall-clock components. Seconds allow 60 for RFC 3339 leap seconds.
function isValidHms(hour, minute, second) {
return hour <= 23 && minute <= 59 && second <= 60;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

second <= 60 (leap second) is only reachable via time — date-time always calls this with second hardcoded to 0 (the regex fixes seconds to 00), so leap-second tolerance is dead for date-time and live-but-undocumented for time. Not wrong, just worth confirming that's intended (a time value of 23:59:60 currently validates).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, good point. Went the strict route and dropped leap seconds entirely, so seconds max out at 59 for time. Keeps it consistent with date-time, which only stores zero seconds anyway, and it also fixed a small bug where stuff like 12:00:60 was slipping through. Added tests for 23:59:60 and 12:00:60 both getting rejected. Easy to loosen later if anyone ever actually needs it.

expect(when.kind).to.equal('string');
expect(when.format).to.equal(format);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Given the description explicitly calls out "if enum is also present, enum applies and format has no effect," a direct test for that combination (string with both enum and format: date) would close the loop — right now it's only asserted in prose.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added it. It was only asserted in prose before, so good call.

@kozmaadrian
kozmaadrian merged commit 9e1e048 into main Sep 4, 2026
5 checks passed
@kozmaadrian
kozmaadrian deleted the date-time-fields branch September 4, 2026 14:42
github-actions Bot pushed a commit that referenced this pull request Sep 4, 2026
# [0.5.0](v0.4.0...v0.5.0) (2026-09-04)

### Features

* **engine:** support date, time and date-time formats ([#15](#15)) ([9e1e048](9e1e048))
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants