Skip to content

WW-5811 Derive date min/max and temporal required HTML5 constraints - #2061

Merged
lukaszlenart merged 4 commits into
mainfrom
WW-5811-temporal-html5-constraints
Oct 25, 2026
Merged

lukaszlenart merged 4 commits into
mainfrom
WW-5811-temporal-html5-constraints

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5811

What changes

HTML5 constraint derivation (struts.ui.html5.constraints, html5 theme) now covers temporal controls:

  • min/max on date controls from a range validator whose bounds are LocalDate (localdate, WW-5816) or java.util.Date (date), written as yyyy-MM-dd.
    • A java.util.Date bound is reduced to its calendar date in the default time zone, which is the zone DateConverter parses the submitted yyyy-MM-dd in.
    • Dropping the time never makes the browser stricter than the server. With a min of 10:00 on the 1st, the browser allows the 1st and the server rejects it, which is the permitted direction. A max day submits as midnight, which is ≤ max.
    • Years outside 0001–9999 are skipped. LocalDate.toString() writes +10000-…, which is not a valid HTML date.
  • required on temporal controls (date, month, week, time, datetime-local) from a required validator, when the bound property's declared type converts "" to null:
    • The types are java.util.Date and its subclasses, Calendar, LocalDate, LocalDateTime, LocalTime and OffsetDateTime.
    • An empty submit then fails on both sides.
    • A String property keeps "" and so still gets no required.

Design decisions (agreed on the ticket discussion)

  • Scope is cut to the date control for ranges. No built-in converter and range validator pair round-trips the other temporal controls:
    • datetime-local submits without seconds, which java.util.Date does not parse.
    • time has no LocalTime range validator.
    • month and week have no converter at all.
    • Emitting attributes nothing on the server matches would be untestable, so these controls are left for a future ticket if wanted.
  • SPI compatibility. HtmlConstraintProvider (public since 7.4.0) gains a default constraintsFor(..., Class<?> propertyType) overload that delegates to the 7.4.0 method, so existing providers compile and behave unchanged. UIBean calls the new overload.
  • Fixed list of types, not a trial conversion. The list lives in the protected convertsBlankToNull(Class), so an app whose own converter maps "" to a value can override one method.
  • Property type lookup. Form.getPropertyType resolves the type on the object parameter binding writes to (the pushed model, then the action). It then walks declared types, so a null intermediate object such as a not-yet-created user does not hide it. An indexed or unknown path gives null, which means no required.
  • Existing protected method signatures are untouched. The new behaviour lives in new methods (addDateRange, addTemporalRequired, convertsBlankToNull), so subclass overrides keep being called.

Migration note for 7.5.0

A subclass of StrutsHtmlConstraintProvider that overrides the public 4-argument constraintsFor is now bypassed, because the framework calls the 5-argument overload. Such a subclass should override the 5-argument method instead. Providers that implement the interface directly are unaffected.

Tests

  • StrutsHtmlConstraintProviderTest:
    • LocalDate and java.util.Date bounds. The Date case pins Asia/Tokyo with a bound whose UTC date differs, so it cannot pass vacuously on a UTC machine.
    • Years out of range, and the other temporal controls getting no min/max.
    • required for each listed type on each temporal control.
    • No required for String, for an unknown type, or on a text control.
    • A provider implementing only the 4-argument method still receives the call.
  • ConstraintAttributesTest: the property type handed to the provider for a top-level field, a nested field through null objects, a model-driven field, and indexed and unknown fields.
  • Html5ConstraintRenderingTest:
    • Replaces testRendersNoRequiredOnADateTextField, as the ticket asks.
    • Covers datetextfield and textfield type="date" rendering required, min and max, and a String-bound date input rendering no required.
  • The rendering tests were checked by mutation: disabling the type check or the date-range branch fails them.
  • Full core suite passes on JDK 17 and JDK 25 (3610 tests).

Docs: a struts-site PR follows for core-developers/client-side-validation.md, whose mapping table still says date ranges are deferred.

🤖 Generated with Claude Code

lukaszlenart and others added 2 commits October 25, 2026 15:34
…aints

The date control now gets min/max from a range validator whose bounds
are LocalDate (localdate) or java.util.Date (date). A Date bound is
reduced to its calendar date in the default time zone, the zone the
submitted yyyy-MM-dd is parsed in; dropping the time never makes the
browser stricter than the server. Years outside 0001-9999 are skipped.

A required validator on a temporal control now emits required when the
bound property's type converts "" to null (Date and subclasses,
Calendar, LocalDate, LocalDateTime, LocalTime, OffsetDateTime), since
the empty submit then fails on both sides. A String property keeps "",
so it still gets no required.

The provider needs the property type for that, so HtmlConstraintProvider
gains a default constraintsFor overload carrying it, which delegates to
the 7.4.0 method; UIBean calls the new one. Form resolves the type on the
object binding writes to (the pushed model, then the action) and walks
declared types, so a null intermediate object does not hide it; an
indexed or unknown path gives null and no required.

month, week, time and datetime-local get no min/max: no built-in
converter and range validator pair round-trips their submitted values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d to end

A date validator with XML bounds on a java.util.Date property renders the
configured dates under a non-UTC default zone; converting the bound in
UTC instead renders 2025-12-31.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

Derived constraints can reject server-valid submissions, and date-range dispatch bypasses existing protected overrides.

4 open issues
What changed in this PR

Extends Struts’ HTML5 constraint derivation to temporal controls, using validators and declared property types to generate browser attributes.

Changes:

  • Derives date min/max bounds and type-aware temporal required attributes.
  • Adds a property-type-aware provider overload and binding-type lookup.
  • Expands provider, property-resolution, and rendering tests.
File Description
core/​src/​test/​resources/​org/​apache/​struts2/​components/​ConstraintAction-validation.xml Adds temporal validation fixtures.
core/​src/​test/​java/​org/​apache/​struts2/​views/​jsp/​ui/​Html5ConstraintRenderingTest.java Tests rendered date bounds and required attributes.
core/​src/​test/​java/​org/​apache/​struts2/​components/​StrutsHtmlConstraintProviderTest.java Covers temporal constraints and provider compatibility.
core/​src/​test/​java/​org/​apache/​struts2/​components/​ConstraintAttributesTest.java Tests property types passed to providers.
core/​src/​test/​java/​org/​apache/​struts2/​components/​ConstraintAction.java Adds date and string fixture properties.
core/​src/​main/​java/​org/​apache/​struts2/​components/​UIBean.java Passes property types to constraint providers.
core/​src/​main/​java/​org/​apache/​struts2/​components/​StrutsHtmlConstraintProvider.java Implements temporal constraint derivation.
core/​src/​main/​java/​org/​apache/​struts2/​components/​HtmlControlType.java Adds temporal-control classification.
core/​src/​main/​java/​org/​apache/​struts2/​components/​HtmlConstraintProvider.java Adds the property-type-aware overload.
core/​src/​main/​java/​org/​apache/​struts2/​components/​Form.java Resolves declared binding-property types.

Review effort: Balanced

Comment thread core/src/main/java/org/apache/struts2/components/Form.java
lukaszlenart and others added 2 commits October 25, 2026 17:12
…e server accepts

- The property type is read on the form's submission action class. A
  different rendering action could declare the same name with another
  type; a ModelDriven submission action it does not render gives null,
  as its model type is only known at runtime.
- DATE ranges dispatch through addRange again, so a 7.4.0 subclass
  overriding it keeps receiving them.
- A java.util.Date bound before the Gregorian cutover is skipped:
  DateConverter reads earlier yyyy-MM-dd values as Julian dates, so
  1500-01-01 rendered as min="1500-01-10".
- A field with its own step gets no derived min, which would become the
  step base and move the developer's grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r S2259

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@lukaszlenart
lukaszlenart merged commit 1d93420 into main Oct 25, 2026
8 checks passed
@lukaszlenart
lukaszlenart deleted the WW-5811-temporal-html5-constraints branch October 25, 2026 20:13
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.

2 participants