diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java index 18068d334..87a0f06cc 100644 --- a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java @@ -23,7 +23,9 @@ import java.lang.reflect.Member; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; @@ -88,14 +90,15 @@ public class DateConverter extends DefaultTypeConverter { } catch (ParseException ignore) { } } - } else if (java.time.LocalDateTime.class == toType) { + } else if (java.time.LocalDateTime.class == toType || java.time.LocalDate.class == toType + || java.time.LocalTime.class == toType) { DateTimeFormatter dtf = null; - TemporalAccessor check; + TemporalAccessor check = null; DateTimeFormatter[] dfs = getDateTimeFormats(ActionContext.of(context), locale); for (DateTimeFormatter df1 : dfs) { try { - check = df1.parse(sa); + check = df1.parseBest(sa, LocalDateTime::from, LocalDate::from, LocalTime::from); dtf = df1; if (check != null) { break; @@ -103,15 +106,20 @@ public class DateConverter extends DefaultTypeConverter { } catch (DateTimeParseException ignore) { } } - if (dtf == null) { - throw new TypeConversionException("Could not parse date"); - } else { - try { + try { + if (dtf != null && check instanceof LocalDateTime) { return LocalDateTime.parse(sa, dtf); - } catch (DateTimeParseException e) { - throw new TypeConversionException("Could not parse date", e); + } else if (dtf != null && check instanceof LocalDate) { + return LocalDate.parse(sa, dtf); + } else if (dtf != null && check instanceof LocalTime) { + return LocalTime.parse(sa, dtf); + } else { + throw new TypeConversionException("Could not parse date"); } + } catch (DateTimeParseException e) { + throw new TypeConversionException("Could not parse date", e); } + } // final fallback for dates without time @@ -179,7 +187,6 @@ public class DateConverter extends DefaultTypeConverter { private DateFormat[] getDateFormats(ActionContext context, Locale locale) { DateFormat globalDateFormat = null; String globalFormat = getGlobalDateString(context); - // if (globalFormat != null) { globalDateFormat = new SimpleDateFormat(globalFormat, locale); } @@ -216,9 +223,23 @@ public class DateConverter extends DefaultTypeConverter { */ protected DateTimeFormatter[] getDateTimeFormats(ActionContext context, Locale locale) { - DateTimeFormatter df1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + DateTimeFormatter globalDateFormat = null; + String globalFormat = getGlobalDateString(context); + if (globalFormat != null) { + globalDateFormat = DateTimeFormatter.ofPattern(globalFormat, locale); + } - final DateTimeFormatter[] dateFormats = new DateTimeFormatter[] { df1 }; + DateTimeFormatter df1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + DateTimeFormatter df2 = DateTimeFormatter.ISO_LOCAL_DATE; + DateTimeFormatter df3 = DateTimeFormatter.ISO_LOCAL_TIME; + + final DateTimeFormatter[] dateFormats; + + if (globalDateFormat == null) { + dateFormats = new DateTimeFormatter[] { df1, df2, df3 }; + } else { + dateFormats = new DateTimeFormatter[] { globalDateFormat, df1, df2, df3 }; + } return dateFormats; } diff --git a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java index fa954661e..cf91282f0 100644 --- a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java @@ -28,7 +28,9 @@ import org.apache.struts2.conversion.TypeConversionException; import java.sql.Time; import java.sql.Timestamp; import java.text.DateFormat; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Date; import java.util.HashMap; import java.util.Locale; @@ -45,7 +47,11 @@ public class DateConverterTest extends StrutsInternalTestCase { private final static String DATE_CONVERTED = "Fri Mar 20 00:00:00"; private final static String INVALID_DATE = "99/99/2010"; private final static String LOCALDATETIME_STR = "2020-03-20T00:00:00.000000"; + private final static String LOCALDATETIME1_STR = "12:00 AM Fri Mar 20, 2020"; private final static String LOCALDATETIME_CONVERTED = "2020-03-20T00:00"; + private final static String LOCALDATE_STR = "2020-03-20"; + private final static String LOCALTIME_STR = "01:59:10"; + private final static String INVALID_LOCALDATETIME = "2010-99-99T00:00"; private final static String MESSAGE_PARSE_ERROR = "Could not parse date"; private final static String MESSAGE_DEFAULT_CONSTRUCTOR_ERROR = "Couldn't create class null using default (long) constructor"; @@ -53,8 +59,7 @@ public class DateConverterTest extends StrutsInternalTestCase { public void testSqlTimeType() { DateConverter converter = new DateConverter(); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(mxLocale); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(mxLocale); Object value = converter.convertValue(context.getContextMap(), null, null, null, TIME_01_59_10, Time.class); assertEquals("01:59:10", value.toString()); @@ -63,10 +68,10 @@ public class DateConverterTest extends StrutsInternalTestCase { public void testSqlTimestampType() { DateConverter converter = new DateConverter(); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(mxLocale); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(mxLocale); - Object value = converter.convertValue(context.getContextMap(), null, null, null, INPUT_TIME_STAMP_STR, Timestamp.class); + Object value = converter.convertValue(context.getContextMap(), null, null, null, INPUT_TIME_STAMP_STR, + Timestamp.class); assertEquals(RES_TIME_STAMP_STR, value.toString()); } @@ -78,9 +83,8 @@ public class DateConverterTest extends StrutsInternalTestCase { ValueStack stack = new StubValueStack(); stack.push(new StubTextProvider(map)); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(new Locale("es_MX", "MX")) - .withValueStack(stack); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(new Locale("es_MX", "MX")) + .withValueStack(stack); Object value = converter.convertValue(context.getContextMap(), null, null, null, DATE_STR, Date.class); assertTrue(value.toString().startsWith(DATE_CONVERTED)); @@ -94,9 +98,8 @@ public class DateConverterTest extends StrutsInternalTestCase { ValueStack stack = new StubValueStack(); stack.push(new StubTextProvider(map)); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(new Locale("es_MX", "MX")) - .withValueStack(stack); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(new Locale("es_MX", "MX")) + .withValueStack(stack); try { converter.convertValue(context.getContextMap(), null, null, null, INVALID_DATE, Date.class); @@ -110,8 +113,7 @@ public class DateConverterTest extends StrutsInternalTestCase { public void testTypeConversionExceptionWhenUsingLongConstructor() { DateConverter converter = new DateConverter(); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(mxLocale); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(mxLocale); try { converter.convertValue(context.getContextMap(), null, null, null, INPUT_WHEN_LONG_CONSTRUCTOR_STR, null); @@ -125,46 +127,70 @@ public class DateConverterTest extends StrutsInternalTestCase { public void testLocalDateTimeType() { DateConverter converter = new DateConverter(); + ActionContext context = ActionContext.of(new HashMap<>()); + + Object value = converter.convertValue(context.getContextMap(), null, null, null, LOCALDATETIME_STR, + LocalDateTime.class); + assertTrue(value.toString().startsWith(LOCALDATETIME_CONVERTED)); + } + + public void testLocalDateTime1Type() { + DateConverter converter = new DateConverter(); + Map map = new HashMap<>(); - map.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, "yyyy-MM-dd"); + map.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, "hh:mm a EEE MMM dd, yyyy"); ValueStack stack = new StubValueStack(); stack.push(new StubTextProvider(map)); - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(new Locale("es_MX", "MX")) - .withValueStack(stack); + ActionContext context = ActionContext.of(new HashMap<>()).withLocale(mxLocale) + .withValueStack(stack); - Object value = converter.convertValue(context.getContextMap(), null, null, null, LOCALDATETIME_STR, LocalDateTime.class); + Object value = converter.convertValue(context.getContextMap(), null, null, null, LOCALDATETIME1_STR, + LocalDateTime.class); assertTrue(value.toString().startsWith(LOCALDATETIME_CONVERTED)); } public void testLocalDateTimeTypeConversionExceptionWhenParseError() { DateConverter converter = new DateConverter(); - Map map = new HashMap<>(); - map.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, "yyyy-MM-dd"); - ValueStack stack = new StubValueStack(); - stack.push(new StubTextProvider(map)); - - ActionContext context = ActionContext.of(new HashMap<>()) - .withLocale(new Locale("es_MX", "MX")) - .withValueStack(stack); + ActionContext context = ActionContext.of(new HashMap<>()); try { - converter.convertValue(context.getContextMap(), null, null, null, INVALID_LOCALDATETIME, LocalDateTime.class); + converter.convertValue(context.getContextMap(), null, null, null, INVALID_LOCALDATETIME, + LocalDateTime.class); fail("TypeConversionException expected - Conversion error occurred"); } catch (Exception ex) { assertEquals(TypeConversionException.class, ex.getClass()); assertEquals(MESSAGE_PARSE_ERROR, ex.getMessage()); } } + + public void testLocalDateType() { + DateConverter converter = new DateConverter(); + + ActionContext context = ActionContext.of(new HashMap<>()); + + Object value = converter.convertValue(context.getContextMap(), null, null, null, LOCALDATE_STR, + LocalDate.class); + assertTrue(value.toString().startsWith(LOCALDATE_STR)); + } + + public void testLocalTimeType() { + DateConverter converter = new DateConverter(); + + ActionContext context = ActionContext.of(new HashMap<>()); + + Object value = converter.convertValue(context.getContextMap(), null, null, null, LOCALTIME_STR, + LocalTime.class); + assertTrue(value.toString().startsWith(LOCALTIME_STR)); + } @Override protected void setUp() throws Exception { super.setUp(); - //Due to JEP 252: Use CLDR Locale Data by Default + // Due to JEP 252: Use CLDR Locale Data by Default DateFormat dFormat = DateFormat.getDateInstance(DateFormat.SHORT, mxLocale); - if (dFormat.format(new Date()).contains("-")) { // Format when Java 9 or greater + if (dFormat.format(new Date()).contains("-")) { // Format when Java 9 or greater INPUT_TIME_STAMP_STR = "2020-03-20 00:00:00.000"; INPUT_WHEN_LONG_CONSTRUCTOR_STR = "2020-03-20"; } else {// Format when Java 8 or lower