Lukasz Lenart created WW-5686:
---------------------------------
Summary: <s:date/> renders a java.sql.Date with the current
wall-clock time, making DateTest.testJavaSqlDate flaky
Key: WW-5686
URL: https://issues.apache.org/jira/browse/WW-5686
Project: Struts 2
Issue Type: Bug
Reporter: Lukasz Lenart
{{org.apache.struts2.components.Date.end}} fills in the missing time-of-day of
a {{java.sql.Date}} from the current clock:
{code:java}Object dateObject = findValue(name);
if (dateObject instanceof java.sql.Date) {
date = ((java.sql.Date)
dateObject).toLocalDate().atTime(LocalTime.now(tz)).atZone(tz);
} else if (dateObject instanceof java.sql.Time) {
date = ((java.sql.Time)
dateObject).toLocalTime().atDate(ZonedDateTime.now(tz).toLocalDate()).atZone(tz);
}
{code}
Two consequences.
h3. 1. The rendered output is non-deterministic
A {{java.sql.Date}} carries no time-of-day, so any format that includes time
fields renders whatever the wall clock happens to say at render time. The same
value rendered twice in one page produces two different strings.
This was not always the behaviour. WW-5197 (6.0.3) added {{java.sql.Date}}
support as {{toLocalDate().atStartOfDay(tz)}} — deterministic midnight. WW-5272
(commit {{08de24588}}, 6.2.0, "Extends <s:date/> to support
java.sql.Time") changed it to {{atTime(LocalTime.now(tz))}} while adding the
{{java.sql.Time}} branch, and changed {{DateTest}} in the same commit from
{{getDateInstance(MEDIUM)}} to {{getDateTimeInstance(MEDIUM, MEDIUM)}} so the
assertion would cover the newly rendered time. The {{java.sql.Time}} branch
genuinely needs a date to complete a {{ZonedDateTime}}, but the
{{java.sql.Date}} branch has no equivalent need for _now's_ time — midnight
completed it just as well, and did so reproducibly. This looks like collateral
of the {{java.sql.Time}} work rather than an intended change to
{{java.sql.Date}} rendering, and it shipped without a note.
h3. 2. {{DateTest.testJavaSqlDate}} is inherently flaky
The test builds its expected string from {{new
java.sql.Date(System.currentTimeMillis())}}, whose underlying millis still
carry the capture-time time-of-day, and compares it against output whose time
comes from {{LocalTime.now()}} at render. The two disagree whenever the clock
crosses a second boundary between the two calls.
Observed on the SonarCloud job of [PR
#1859|https://github.com/apache/struts/pull/1859] (an unrelated OGNL change):
{code}DateTest.testJavaSqlDate:104 expected:<Aug 23, 2026, 4:37:1[4] PM> but
was:<Aug 23, 2026, 4:37:1[5] PM>
{code}
The same commit passed Build and Test on JDK 17, 21 and 25 — only that one job
happened to straddle the boundary. The failure window is roughly the elapsed
time between the two calls, so it is rare but will keep failing unrelated PRs.
{{testJavaSqlTime}} is not affected in practice: it formats with {{hh:mm:ss}},
and {{toLocalTime()}} preserves the value's own time, so the substituted date
is never rendered. It would only misfire across a midnight boundary.
h3. Suggested fix
Restore {{toLocalDate().atStartOfDay(tz)}} for {{java.sql.Date}} and assert
against a fixed instant in the test rather than the current time. That reverts
{{java.sql.Date}} rendering to its pre-6.2.0 semantics, so it needs a Version
Notes entry.
If the current behaviour is instead considered intentional, the test still has
to stop racing the clock — inject a fixed clock or assert only the date fields
— and the substitution should be documented on the tag.
_Affects:_ 6.2.0 onwards, on every maintained line.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)