This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 53eb6f748ab22aaad5a9dbb781f67e6166288911
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Oct 24 16:18:20 2026 +0200

    WW-5810 fix(javatemplates): render java.time in the interceptor's calendar
    
    DateTimeFormatter always uses the ISO calendar, while
    DateTextFieldInterceptor parses with SimpleDateFormat, which follows the
    default locale's calendar. Under th-TH a LocalDate of 2026-10-24
    rendered year 2026 and bound back as 1483-11-02. java.time values are
    now converted to a Date and formatted with SimpleDateFormat like the
    Date path; a field the type does not have still renders empty.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../views/java/simple/DateTextFieldHandler.java    | 48 +++++++++++++++++-----
 .../views/java/simple/DateTextFieldTest.java       | 38 +++++++++++++++++
 2 files changed, 75 insertions(+), 11 deletions(-)

diff --git 
a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
 
b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
index 007d4f2b5..edc255faf 100644
--- 
a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
+++ 
b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
@@ -20,9 +20,12 @@ package org.apache.struts2.views.java.simple;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.time.format.DateTimeFormatter;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoField;
 import java.time.temporal.TemporalAccessor;
-import java.time.temporal.UnsupportedTemporalTypeException;
 import java.util.Date;
 import java.util.Map;
 
@@ -104,17 +107,40 @@ public class DateTextFieldHandler extends 
AbstractTagHandler implements TagGener
     }
 
     protected String formatValue(Object value, DateWord dateWord) {
-        if (value instanceof Date date) {
-            return new SimpleDateFormat(dateWord.getDateType()).format(date);
+        Date date = toDate(value);
+        if (date == null || (value instanceof TemporalAccessor temporal && 
!temporal.isSupported(toField(dateWord)))) {
+            return null;
         }
-        if (value instanceof TemporalAccessor temporal) {
-            try {
-                return 
DateTimeFormatter.ofPattern(dateWord.getDateType()).format(temporal);
-            } catch (UnsupportedTemporalTypeException e) {
-                return null;
-            }
+        // DateTextFieldInterceptor parses with SimpleDateFormat, so render in 
its calendar (Buddhist under th-TH)
+        return new SimpleDateFormat(dateWord.getDateType()).format(date);
+    }
+
+    private static Date toDate(Object value) {
+        LocalDateTime dateTime;
+        if (value instanceof Date date) {
+            return date;
+        } else if (value instanceof LocalDate localDate) {
+            dateTime = localDate.atStartOfDay();
+        } else if (value instanceof LocalDateTime localDateTime) {
+            dateTime = localDateTime;
+        } else if (value instanceof LocalTime localTime) {
+            dateTime = localTime.atDate(LocalDate.EPOCH);
+        } else {
+            return null;
         }
-        return null;
+        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    private static ChronoField toField(DateWord dateWord) {
+        return switch (dateWord) {
+            case S -> ChronoField.MILLI_OF_SECOND;
+            case s -> ChronoField.SECOND_OF_MINUTE;
+            case m -> ChronoField.MINUTE_OF_HOUR;
+            case H -> ChronoField.HOUR_OF_DAY;
+            case d -> ChronoField.DAY_OF_MONTH;
+            case M -> ChronoField.MONTH_OF_YEAR;
+            case y -> ChronoField.YEAR;
+        };
     }
     
     
diff --git 
a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
 
b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
index 07c24bf98..8545ae44a 100644
--- 
a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
+++ 
b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
@@ -20,7 +20,12 @@
  */
 package org.apache.struts2.views.java.simple;
 
+import org.apache.struts2.ActionContext;
 import org.apache.struts2.components.DateTextField;
+import org.apache.struts2.conversion.impl.DateConverter;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.interceptor.DateTextFieldInterceptor;
+import org.apache.struts2.mock.MockActionInvocation;
 import org.apache.struts2.components.UIBean;
 import org.apache.struts2.interceptor.csp.CspNonceSource;
 import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
@@ -30,6 +35,11 @@ import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class DateTextFieldTest extends AbstractCommonAttributesTest {
 
@@ -91,6 +101,34 @@ public class DateTextFieldTest extends 
AbstractCommonAttributesTest {
             + "</div>"), output);
     }
 
+    public void testLocalDateRoundTripsUnderABuddhistCalendarLocale() throws 
Exception {
+        Locale previous = Locale.getDefault(Locale.Category.FORMAT);
+        Locale.setDefault(Locale.Category.FORMAT, 
Locale.forLanguageTag("th-TH"));
+        try {
+            String output = render("yyyy-MM-dd", LocalDate.of(2026, 10, 24));
+
+            assertEquals(LocalDate.of(2026, 10, 24), submit(output, 
LocalDate.class));
+        } finally {
+            Locale.setDefault(Locale.Category.FORMAT, previous);
+        }
+    }
+
+    private Object submit(String output, Class<?> toType) throws Exception {
+        Map<String, Object> params = new HashMap<>();
+        Matcher field = Pattern.compile("name=\"(__[a-z]+_name)\" 
value=\"([^\"]*)\"").matcher(output);
+        while (field.find()) {
+            params.put(field.group(1), new String[]{field.group(2)});
+        }
+        ActionContext actionContext = 
ActionContext.of().withParameters(HttpParameters.create(params).build());
+        MockActionInvocation invocation = new MockActionInvocation();
+        invocation.setInvocationContext(actionContext);
+
+        new DateTextFieldInterceptor().intercept(invocation);
+
+        Object bound = actionContext.getParameters().get("name").getObject();
+        return new DateConverter().convertValue(actionContext.getContextMap(), 
null, null, null, bound, toType);
+    }
+
     private String render(String format, Object value) {
         tag.setId("id");
         tag.setName("name");

Reply via email to