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)

Reply via email to