diff --git a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
index 0b3b351405..54168efb5f 100644
--- a/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
+++ b/spring-context/src/main/java/org/springframework/format/annotation/DateTimeFormat.java
@@ -26,17 +26,20 @@ import java.lang.annotation.Target;
* Declares that a field or method parameter should be formatted as a date or time.
*
*
Supports formatting by style pattern, ISO date time pattern, or custom format pattern string.
- * Can be applied to {@code java.util.Date}, {@code java.util.Calendar}, {@code Long} (for
- * millisecond timestamps) as well as JSR-310 java.time
and Joda-Time value types.
+ * Can be applied to {@link java.util.Date}, {@link java.util.Calendar}, {@link Long} (for
+ * millisecond timestamps) as well as JSR-310 {@code java.time} and Joda-Time value types.
*
- *
For style-based formatting, set the {@link #style} attribute to be the style pattern code.
+ *
For style-based formatting, set the {@link #style} attribute to the desired style pattern code.
* The first character of the code is the date style, and the second character is the time style.
* Specify a character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' for full.
- * A date or time may be omitted by specifying the style character '-'.
+ * The date or time may be omitted by specifying the style character '-' — for example,
+ * 'M-' specifies a medium format for the date with no time.
*
- *
For ISO-based formatting, set the {@link #iso} attribute to be the desired {@link ISO} format,
- * such as {@link ISO#DATE}. For custom formatting, set the {@link #pattern} attribute to be the
- * DateTime pattern, such as {@code "yyyy/MM/dd hh:mm:ss a"}.
+ *
For ISO-based formatting, set the {@link #iso} attribute to the desired {@link ISO} format,
+ * such as {@link ISO#DATE}.
+ *
+ *
For custom formatting, set the {@link #pattern} attribute to a date time pattern, such as
+ * {@code "yyyy/MM/dd hh:mm:ss a"}.
*
*
Each attribute is mutually exclusive, so only set one attribute per annotation instance
* (the one most convenient for your formatting needs).
@@ -48,8 +51,19 @@ import java.lang.annotation.Target;
* with a style code of 'SS' (short date, short time).
*
*
+ *
Time Zones
+ * Whenever the {@link #style} or {@link #pattern} attribute is used, the
+ * {@linkplain java.util.TimeZone#getDefault() default time zone} of the JVM will
+ * be used when formatting {@link java.util.Date} values. Whenever the {@link #iso}
+ * attribute is used when formatting {@link java.util.Date} values, {@code UTC}
+ * will be used as the time zone. The same time zone will be applied to any
+ * {@linkplain #fallbackPatterns fallback patterns} as well. In order to enforce
+ * consistent use of {@code UTC} as the time zone, you can bootstrap the JVM with
+ * {@code -Duser.timezone=UTC}.
+ *
* @author Keith Donald
* @author Juergen Hoeller
+ * @author Sam Brannen
* @since 3.0
* @see java.time.format.DateTimeFormatter
* @see org.joda.time.format.DateTimeFormat
@@ -60,34 +74,59 @@ import java.lang.annotation.Target;
public @interface DateTimeFormat {
/**
- * The style pattern to use to format the field.
- *
Defaults to 'SS' for short date time. Set this attribute when you wish to format
- * your field in accordance with a common style other than the default style.
+ * The style pattern to use to format the field or method parameter.
+ *
Defaults to 'SS' for short date, short time. Set this attribute when you
+ * wish to format your field or method parameter in accordance with a common
+ * style other than the default style.
+ * @see #fallbackPatterns
*/
String style() default "SS";
/**
- * The ISO pattern to use to format the field.
- *
The possible ISO patterns are defined in the {@link ISO} enum.
+ * The ISO pattern to use to format the field or method parameter.
+ *
Supported ISO patterns are defined in the {@link ISO} enum.
*
Defaults to {@link ISO#NONE}, indicating this attribute should be ignored.
- * Set this attribute when you wish to format your field in accordance with an ISO format.
+ * Set this attribute when you wish to format your field or method parameter
+ * in accordance with an ISO format.
+ * @see #fallbackPatterns
*/
ISO iso() default ISO.NONE;
/**
- * The custom pattern to use to format the field.
- *
Defaults to empty String, indicating no custom pattern String has been specified.
- * Set this attribute when you wish to format your field in accordance with a custom
- * date time pattern not represented by a style or ISO format.
+ * The custom pattern to use to format the field or method parameter.
+ *
Defaults to empty String, indicating no custom pattern String has been
+ * specified. Set this attribute when you wish to format your field or method
+ * parameter in accordance with a custom date time pattern not represented by
+ * a style or ISO format.
*
Note: This pattern follows the original {@link java.text.SimpleDateFormat} style,
* as also supported by Joda-Time, with strict parsing semantics towards overflows
* (e.g. rejecting a Feb 29 value for a non-leap-year). As a consequence, 'yy'
* characters indicate a year in the traditional style, not a "year-of-era" as in the
* {@link java.time.format.DateTimeFormatter} specification (i.e. 'yy' turns into 'uu'
- * when going through that {@code DateTimeFormatter} with strict resolution mode).
+ * when going through a {@code DateTimeFormatter} with strict resolution mode).
+ * @see #fallbackPatterns
*/
String pattern() default "";
+ /**
+ * The set of custom patterns to use as a fallback in case parsing fails for
+ * the primary {@link #pattern}, {@link #iso}, or {@link #style} attribute.
+ *
For example, if you wish to use the ISO date format for parsing and
+ * printing but allow for lenient parsing of user input for various date
+ * formats, you could configure something similar to the following.
+ *
+ * {@literal @}DateTimeFormat(iso = ISO.DATE, fallbackPatterns = { "M/d/yy", "dd.MM.yyyy" })
+ *
+ * Fallback patterns are only used for parsing. They are not used for
+ * printing the value as a String. The primary {@link #pattern}, {@link #iso},
+ * or {@link #style} attribute is always used for printing. For details on
+ * which time zone is used for fallback patterns, see the
+ * {@linkplain DateTimeFormat class-level documentation}.
+ *
Fallback patterns are not supported for Joda-Time value types.
+ * @since 5.3.5
+ */
+ String[] fallbackPatterns() default {};
+
/**
* Common ISO date time format patterns.
@@ -95,20 +134,20 @@ public @interface DateTimeFormat {
enum ISO {
/**
- * The most common ISO Date Format {@code yyyy-MM-dd},
- * e.g. "2000-10-31".
+ * The most common ISO Date Format {@code yyyy-MM-dd} — for example,
+ * "2000-10-31".
*/
DATE,
/**
- * The most common ISO Time Format {@code HH:mm:ss.SSSXXX},
- * e.g. "01:30:00.000-05:00".
+ * The most common ISO Time Format {@code HH:mm:ss.SSSXXX} — for example,
+ * "01:30:00.000-05:00".
*/
TIME,
/**
- * The most common ISO DateTime Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX},
- * e.g. "2000-10-31T01:30:00.000-05:00".
+ * The most common ISO Date Time Format {@code yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
+ * — for example, "2000-10-31T01:30:00.000-05:00".
*/
DATE_TIME,
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java
index bef0684837..d69e9c15e5 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,17 +27,21 @@ import java.util.Map;
import java.util.TimeZone;
import org.springframework.format.Formatter;
+import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.lang.Nullable;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* A formatter for {@link java.util.Date} types.
- * Allows the configuration of an explicit date pattern and locale.
+ *
Supports the configuration of an explicit date time pattern, timezone,
+ * locale, and fallback date time patterns for lenient parsing.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Phillip Webb
+ * @author Sam Brannen
* @since 3.0
* @see SimpleDateFormat
*/
@@ -56,9 +60,15 @@ public class DateFormatter implements Formatter {
}
+ @Nullable
+ private Object source;
+
@Nullable
private String pattern;
+ @Nullable
+ private String[] fallbackPatterns;
+
private int style = DateFormat.DEFAULT;
@Nullable
@@ -74,19 +84,33 @@ public class DateFormatter implements Formatter {
/**
- * Create a new default DateFormatter.
+ * Create a new default {@code DateFormatter}.
*/
public DateFormatter() {
}
/**
- * Create a new DateFormatter for the given date pattern.
+ * Create a new {@code DateFormatter} for the given date time pattern.
*/
public DateFormatter(String pattern) {
this.pattern = pattern;
}
+ /**
+ * Set the source of the configuration for this {@code DateFormatter} —
+ * for example, an instance of the {@link DateTimeFormat @DateTimeFormat}
+ * annotation if such an annotation was used to configure this {@code DateFormatter}.
+ * The supplied source object will only be used for descriptive purposes
+ * by invoking its {@code toString()} method — for example, when
+ * generating an exception message to provide further context.
+ * @param source the source of the configuration
+ * @since 5.3.5
+ */
+ public void setSource(Object source) {
+ this.source = source;
+ }
+
/**
* Set the pattern to use to format date values.
*
If not specified, DateFormat's default style will be used.
@@ -96,7 +120,19 @@ public class DateFormatter implements Formatter {
}
/**
- * Set the ISO format used for this date.
+ * Set additional patterns to use as a fallback in case parsing fails for the
+ * configured {@linkplain #setPattern pattern}, {@linkplain #setIso ISO format},
+ * {@linkplain #setStyle style}, or {@linkplain #setStylePattern style pattern}.
+ * @param fallbackPatterns the fallback parsing patterns
+ * @since 5.3.5
+ * @see DateTimeFormat#fallbackPatterns()
+ */
+ public void setFallbackPatterns(String... fallbackPatterns) {
+ this.fallbackPatterns = fallbackPatterns;
+ }
+
+ /**
+ * Set the ISO format to use to format date values.
* @param iso the {@link ISO} format
* @since 3.2
*/
@@ -105,7 +141,7 @@ public class DateFormatter implements Formatter {
}
/**
- * Set the style to use to format date values.
+ * Set the {@link DateFormat} style to use to format date values.
* If not specified, DateFormat's default style will be used.
* @see DateFormat#DEFAULT
* @see DateFormat#SHORT
@@ -118,8 +154,10 @@ public class DateFormatter implements Formatter {
}
/**
- * Set the two character to use to format date values. The first character used for
- * the date style, the second is for the time style. Supported characters are
+ * Set the two characters to use to format date values.
+ * The first character is used for the date style; the second is used for
+ * the time style.
+ *
Supported characters:
*
* - 'S' = Small
* - 'M' = Medium
@@ -136,7 +174,7 @@ public class DateFormatter implements Formatter {
}
/**
- * Set the TimeZone to normalize the date values into, if any.
+ * Set the {@link TimeZone} to normalize the date values into, if any.
*/
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
@@ -159,12 +197,43 @@ public class DateFormatter implements Formatter {
@Override
public Date parse(String text, Locale locale) throws ParseException {
- return getDateFormat(locale).parse(text);
+ try {
+ return getDateFormat(locale).parse(text);
+ }
+ catch (ParseException ex) {
+ if (!ObjectUtils.isEmpty(this.fallbackPatterns)) {
+ for (String pattern : this.fallbackPatterns) {
+ try {
+ DateFormat dateFormat = configureDateFormat(new SimpleDateFormat(pattern, locale));
+ // Align timezone for parsing format with printing format if ISO is set.
+ if (this.iso != null && this.iso != ISO.NONE) {
+ dateFormat.setTimeZone(UTC);
+ }
+ return dateFormat.parse(text);
+ }
+ catch (ParseException ignoredException) {
+ // Ignore fallback parsing exceptions since the exception thrown below
+ // will include information from the "source" if available -- for example,
+ // the toString() of a @DateTimeFormat annotation.
+ }
+ }
+ }
+ if (this.source != null) {
+ throw new ParseException(
+ String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
+ ex.getErrorOffset());
+ }
+ // else rethrow original exception
+ throw ex;
+ }
}
protected DateFormat getDateFormat(Locale locale) {
- DateFormat dateFormat = createDateFormat(locale);
+ return configureDateFormat(createDateFormat(locale));
+ }
+
+ private DateFormat configureDateFormat(DateFormat dateFormat) {
if (this.timeZone != null) {
dateFormat.setTimeZone(this.timeZone);
}
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateTimeFormatAnnotationFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/DateTimeFormatAnnotationFormatterFactory.java
index 7b31fd6f4b..4c46cd8d0b 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/DateTimeFormatAnnotationFormatterFactory.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/DateTimeFormatAnnotationFormatterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,12 @@
package org.springframework.format.datetime;
+import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import org.springframework.context.support.EmbeddedValueResolutionSupport;
@@ -34,6 +36,7 @@ import org.springframework.util.StringUtils;
* Formats fields annotated with the {@link DateTimeFormat} annotation using a {@link DateFormatter}.
*
* @author Phillip Webb
+ * @author Sam Brannen
* @since 3.2
* @see org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory
*/
@@ -68,15 +71,30 @@ public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueReso
protected Formatter getFormatter(DateTimeFormat annotation, Class> fieldType) {
DateFormatter formatter = new DateFormatter();
+ formatter.setSource(annotation);
+ formatter.setIso(annotation.iso());
+
String style = resolveEmbeddedValue(annotation.style());
if (StringUtils.hasLength(style)) {
formatter.setStylePattern(style);
}
- formatter.setIso(annotation.iso());
+
String pattern = resolveEmbeddedValue(annotation.pattern());
if (StringUtils.hasLength(pattern)) {
formatter.setPattern(pattern);
}
+
+ List resolvedFallbackPatterns = new ArrayList<>();
+ for (String fallbackPattern : annotation.fallbackPatterns()) {
+ String resolvedFallbackPattern = resolveEmbeddedValue(fallbackPattern);
+ if (StringUtils.hasLength(resolvedFallbackPattern)) {
+ resolvedFallbackPatterns.add(resolvedFallbackPattern);
+ }
+ }
+ if (!resolvedFallbackPatterns.isEmpty()) {
+ formatter.setFallbackPatterns(resolvedFallbackPatterns.toArray(new String[0]));
+ }
+
return formatter;
}
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
index 1e79ec61a0..f64cdcb053 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
/**
* A context that holds user-specific java.time
(JSR-310) settings
* such as the user's Chronology (calendar system) and time zone.
- * A {@code null} property value indicate the user has not specified a setting.
+ * A {@code null} property value indicates the user has not specified a setting.
*
* @author Juergen Hoeller
* @since 4.0
@@ -81,8 +81,8 @@ public class DateTimeContext {
/**
- * Get the DateTimeFormatter with the this context's settings
- * applied to the base {@code formatter}.
+ * Get the DateTimeFormatter with this context's settings applied to the
+ * base {@code formatter}.
* @param formatter the base formatter that establishes default
* formatting rules, generally context-independent
* @return the contextual DateTimeFormatter
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContextHolder.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContextHolder.java
index 8877419df9..aa5a5223ef 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContextHolder.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContextHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -69,9 +69,8 @@ public final class DateTimeContextHolder {
return dateTimeContextHolder.get();
}
-
/**
- * Obtain a DateTimeFormatter with user-specific settings applied to the given base Formatter.
+ * Obtain a DateTimeFormatter with user-specific settings applied to the given base formatter.
* @param formatter the base formatter that establishes default formatting rules
* (generally user independent)
* @param locale the current user locale (may be {@code null} if not known)
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
index 3d56f20987..c2fc3148db 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.format.datetime.standard;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
-import java.time.format.ResolverStyle;
import java.util.TimeZone;
import org.springframework.format.annotation.DateTimeFormat.ISO;
@@ -34,6 +33,7 @@ import org.springframework.util.StringUtils;
*
* @author Juergen Hoeller
* @author Phillip Webb
+ * @author Sam Brannen
* @since 4.0
* @see #createDateTimeFormatter()
* @see #createDateTimeFormatter(DateTimeFormatter)
@@ -180,11 +180,7 @@ public class DateTimeFormatterFactory {
public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
DateTimeFormatter dateTimeFormatter = null;
if (StringUtils.hasLength(this.pattern)) {
- // Using strict parsing to align with Joda-Time and standard DateFormat behavior:
- // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected.
- // However, with strict parsing, a year digit needs to be specified as 'u'...
- String patternToUse = StringUtils.replace(this.pattern, "yy", "uu");
- dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
+ dateTimeFormatter = DateTimeFormatterUtils.createStrictDateTimeFormatter(this.pattern);
}
else if (this.iso != null && this.iso != ISO.NONE) {
switch (this.iso) {
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterUtils.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterUtils.java
new file mode 100644
index 0000000000..be767f0e23
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.format.datetime.standard;
+
+import java.time.format.DateTimeFormatter;
+import java.time.format.ResolverStyle;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Internal {@link DateTimeFormatter} utilities.
+ *
+ * @author Juergen Hoeller
+ * @since 5.3.5
+ */
+abstract class DateTimeFormatterUtils {
+
+ static DateTimeFormatter createStrictDateTimeFormatter(String pattern) {
+ // Using strict parsing to align with Joda-Time and standard DateFormat behavior:
+ // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected.
+ // However, with strict parsing, a year digit needs to be specified as 'u'...
+ String patternToUse = StringUtils.replace(pattern, "yy", "uu");
+ return DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/Jsr310DateTimeFormatAnnotationFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/Jsr310DateTimeFormatAnnotationFormatterFactory.java
index cd4d3a9d79..a1fd17fe21 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/Jsr310DateTimeFormatAnnotationFormatterFactory.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/Jsr310DateTimeFormatAnnotationFormatterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,10 @@ import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import org.springframework.context.support.EmbeddedValueResolutionSupport;
@@ -40,6 +42,7 @@ import org.springframework.util.StringUtils;
* JSR-310 java.time
package in JDK 8.
*
* @author Juergen Hoeller
+ * @author Sam Brannen
* @since 4.0
* @see org.springframework.format.annotation.DateTimeFormat
*/
@@ -93,8 +96,17 @@ public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValu
@Override
@SuppressWarnings("unchecked")
public Parser> getParser(DateTimeFormat annotation, Class> fieldType) {
+ List resolvedFallbackPatterns = new ArrayList<>();
+ for (String fallbackPattern : annotation.fallbackPatterns()) {
+ String resolvedFallbackPattern = resolveEmbeddedValue(fallbackPattern);
+ if (StringUtils.hasLength(resolvedFallbackPattern)) {
+ resolvedFallbackPatterns.add(resolvedFallbackPattern);
+ }
+ }
+
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
- return new TemporalAccessorParser((Class extends TemporalAccessor>) fieldType, formatter);
+ return new TemporalAccessorParser((Class extends TemporalAccessor>) fieldType,
+ formatter, resolvedFallbackPatterns.toArray(new String[0]), annotation);
}
/**
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/TemporalAccessorParser.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/TemporalAccessorParser.java
index 4e135eebd8..9f99f2ecf2 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/TemporalAccessorParser.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/TemporalAccessorParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,16 +24,20 @@ import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.util.Locale;
import org.springframework.format.Parser;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ObjectUtils;
/**
* {@link Parser} implementation for a JSR-310 {@link java.time.temporal.TemporalAccessor},
- * using a {@link java.time.format.DateTimeFormatter}) (the contextual one, if available).
+ * using a {@link java.time.format.DateTimeFormatter} (the contextual one, if available).
*
* @author Juergen Hoeller
+ * @author Sam Brannen
* @since 4.0
* @see DateTimeContextHolder#getFormatter
* @see java.time.LocalDate#parse(CharSequence, java.time.format.DateTimeFormatter)
@@ -49,6 +53,12 @@ public final class TemporalAccessorParser implements Parser {
private final DateTimeFormatter formatter;
+ @Nullable
+ private final String[] fallbackPatterns;
+
+ @Nullable
+ private final Object source;
+
/**
* Create a new TemporalAccessorParser for the given TemporalAccessor type.
@@ -57,14 +67,49 @@ public final class TemporalAccessorParser implements Parser {
* @param formatter the base DateTimeFormatter instance
*/
public TemporalAccessorParser(Class extends TemporalAccessor> temporalAccessorType, DateTimeFormatter formatter) {
+ this(temporalAccessorType, formatter, null, null);
+ }
+
+ TemporalAccessorParser(Class extends TemporalAccessor> temporalAccessorType, DateTimeFormatter formatter,
+ @Nullable String[] fallbackPatterns, @Nullable Object source) {
this.temporalAccessorType = temporalAccessorType;
this.formatter = formatter;
+ this.fallbackPatterns = fallbackPatterns;
+ this.source = source;
}
@Override
public TemporalAccessor parse(String text, Locale locale) throws ParseException {
- DateTimeFormatter formatterToUse = DateTimeContextHolder.getFormatter(this.formatter, locale);
+ try {
+ return doParse(text, locale, this.formatter);
+ }
+ catch (DateTimeParseException ex) {
+ if (!ObjectUtils.isEmpty(this.fallbackPatterns)) {
+ for (String pattern : this.fallbackPatterns) {
+ try {
+ DateTimeFormatter fallbackFormatter = DateTimeFormatterUtils.createStrictDateTimeFormatter(pattern);
+ return doParse(text, locale, fallbackFormatter);
+ }
+ catch (DateTimeParseException ignoredException) {
+ // Ignore fallback parsing exceptions since the exception thrown below
+ // will include information from the "source" if available -- for example,
+ // the toString() of a @DateTimeFormat annotation.
+ }
+ }
+ }
+ if (this.source != null) {
+ throw new DateTimeParseException(
+ String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
+ text, ex.getErrorIndex());
+ }
+ // else rethrow original exception
+ throw ex;
+ }
+ }
+
+ private TemporalAccessor doParse(String text, Locale locale, DateTimeFormatter formatter) throws DateTimeParseException {
+ DateTimeFormatter formatterToUse = DateTimeContextHolder.getFormatter(formatter, locale);
if (LocalDate.class == this.temporalAccessorType) {
return LocalDate.parse(text, formatterToUse);
}
diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
index 30b45b1317..ebfbc694dc 100644
--- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
+++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.format.datetime;
+import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@@ -25,16 +26,23 @@ import java.util.Locale;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.TypeMismatchException;
import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionService;
+import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
+import org.springframework.validation.FieldError;
import static org.assertj.core.api.Assertions.assertThat;
@@ -42,10 +50,11 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
* @author Keith Donald
* @author Juergen Hoeller
+ * @author Sam Brannen
*/
public class DateFormattingTests {
- private FormattingConversionService conversionService;
+ private final FormattingConversionService conversionService = new FormattingConversionService();
private DataBinder binder;
@@ -57,7 +66,6 @@ public class DateFormattingTests {
}
private void setup(DateFormatterRegistrar registrar) {
- conversionService = new FormattingConversionService();
DefaultConversionService.addDefaultConverters(conversionService);
registrar.registerFormatters(conversionService);
@@ -87,34 +95,34 @@ public class DateFormattingTests {
@Test
void testBindLongAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("millisAnnotated", "10/31/09");
+ propertyValues.add("styleMillis", "10/31/09");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("millisAnnotated")).isEqualTo("10/31/09");
+ assertThat(binder.getBindingResult().getFieldValue("styleMillis")).isEqualTo("10/31/09");
}
@Test
void testBindCalendarAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("calendarAnnotated", "10/31/09");
+ propertyValues.add("styleCalendar", "10/31/09");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("calendarAnnotated")).isEqualTo("10/31/09");
+ assertThat(binder.getBindingResult().getFieldValue("styleCalendar")).isEqualTo("10/31/09");
}
@Test
void testBindDateAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotated", "10/31/09");
+ propertyValues.add("styleDate", "10/31/09");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("dateAnnotated")).isEqualTo("10/31/09");
+ assertThat(binder.getBindingResult().getFieldValue("styleDate")).isEqualTo("10/31/09");
}
@Test
void testBindDateArray() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotated", new String[]{"10/31/09 12:00 PM"});
+ propertyValues.add("styleDate", new String[]{"10/31/09 12:00 PM"});
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
}
@@ -122,10 +130,10 @@ public class DateFormattingTests {
@Test
void testBindDateAnnotatedWithError() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotated", "Oct X31, 2009");
+ propertyValues.add("styleDate", "Oct X31, 2009");
binder.bind(propertyValues);
- assertThat(binder.getBindingResult().getFieldErrorCount("dateAnnotated")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("dateAnnotated")).isEqualTo("Oct X31, 2009");
+ assertThat(binder.getBindingResult().getFieldErrorCount("styleDate")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("styleDate")).isEqualTo("Oct X31, 2009");
}
@Test
@@ -133,19 +141,19 @@ public class DateFormattingTests {
void testBindDateAnnotatedWithFallbackError() {
// TODO This currently passes because of the Date(String) constructor fallback is used
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotated", "Oct 031, 2009");
+ propertyValues.add("styleDate", "Oct 031, 2009");
binder.bind(propertyValues);
- assertThat(binder.getBindingResult().getFieldErrorCount("dateAnnotated")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("dateAnnotated")).isEqualTo("Oct 031, 2009");
+ assertThat(binder.getBindingResult().getFieldErrorCount("styleDate")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("styleDate")).isEqualTo("Oct 031, 2009");
}
@Test
void testBindDateAnnotatedPattern() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotatedPattern", "10/31/09 1:05");
+ propertyValues.add("patternDate", "10/31/09 1:05");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("dateAnnotatedPattern")).isEqualTo("10/31/09 1:05");
+ assertThat(binder.getBindingResult().getFieldValue("patternDate")).isEqualTo("10/31/09 1:05");
}
@Test
@@ -156,16 +164,17 @@ public class DateFormattingTests {
registrar.setFormatter(dateFormatter);
setup(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotatedPattern", "10/31/09 1:05");
+ propertyValues.add("patternDate", "10/31/09 1:05");
binder.bind(propertyValues);
- assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("dateAnnotatedPattern")).isEqualTo("10/31/09 1:05");
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue("patternDate")).isEqualTo("10/31/09 1:05");
}
@Test
void testBindDateTimeOverflow() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateAnnotatedPattern", "02/29/09 12:00 PM");
+ propertyValues.add("patternDate", "02/29/09 12:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(1);
}
@@ -200,10 +209,10 @@ public class DateFormattingTests {
@Test
void testBindNestedDateAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("children[0].dateAnnotated", "10/31/09");
+ propertyValues.add("children[0].styleDate", "10/31/09");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("children[0].dateAnnotated")).isEqualTo("10/31/09");
+ assertThat(binder.getBindingResult().getFieldValue("children[0].styleDate")).isEqualTo("10/31/09");
}
@Test
@@ -247,35 +256,127 @@ public class DateFormattingTests {
}
+ @Nested
+ class FallbackPatternTests {
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void styleCalendar(String propertyValue) {
+ String propertyName = "styleCalendarWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("3/2/21");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void styleDate(String propertyValue) {
+ String propertyName = "styleDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("3/2/21");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void patternDate(String propertyValue) {
+ String propertyName = "patternDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void isoDate(String propertyValue) {
+ String propertyName = "isoDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
+ }
+
+ @Test
+ void patternDateWithUnsupportedPattern() {
+ String propertyValue = "210302";
+ String propertyName = "patternDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(1);
+ FieldError fieldError = bindingResult.getFieldError(propertyName);
+ assertThat(fieldError.unwrap(TypeMismatchException.class))
+ .hasMessageContaining("for property 'patternDateWithFallbackPatterns'")
+ .hasCauseInstanceOf(ConversionFailedException.class).getCause()
+ .hasMessageContaining("for value '210302'")
+ .hasCauseInstanceOf(IllegalArgumentException.class).getCause()
+ .hasMessageContaining("Parse attempt failed for value [210302]")
+ .hasCauseInstanceOf(ParseException.class).getCause()
+ // Unable to parse date time value "210302" using configuration from
+ // @org.springframework.format.annotation.DateTimeFormat(
+ // pattern=yyyy-MM-dd, style=SS, iso=NONE, fallbackPatterns=[M/d/yy, yyyyMMdd, yyyy.MM.dd])
+ .hasMessageContainingAll(
+ "Unable to parse date time value \"210302\" using configuration from",
+ "@org.springframework.format.annotation.DateTimeFormat",
+ "yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd");
+ }
+ }
+
+
@SuppressWarnings("unused")
private static class SimpleDateBean {
private Long millis;
- private Long millisAnnotated;
+ private Long styleMillis;
- @DateTimeFormat(style="S-")
- private Calendar calendarAnnotated;
+ @DateTimeFormat(style = "S-")
+ private Calendar styleCalendar;
- @DateTimeFormat(style="S-")
- private Date dateAnnotated;
+ @DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
+ private Calendar styleCalendarWithFallbackPatterns;
+
+ @DateTimeFormat(style = "S-")
+ private Date styleDate;
+
+ @DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
+ private Date styleDateWithFallbackPatterns;
+
+ @DateTimeFormat(pattern = "M/d/yy h:mm")
+ private Date patternDate;
- @DateTimeFormat(pattern="M/d/yy h:mm")
- private Date dateAnnotatedPattern;
+ @DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
+ private Date patternDateWithFallbackPatterns;
- @DateTimeFormat(iso=ISO.DATE)
+ @DateTimeFormat(iso = ISO.DATE)
private Date isoDate;
- @DateTimeFormat(iso=ISO.TIME)
+ @DateTimeFormat(iso = ISO.DATE, fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
+ private Date isoDateWithFallbackPatterns;
+
+ @DateTimeFormat(iso = ISO.TIME)
private Date isoTime;
- @DateTimeFormat(iso=ISO.DATE_TIME)
+ @DateTimeFormat(iso = ISO.DATE_TIME)
private Date isoDateTime;
private final List children = new ArrayList<>();
+
public Long getMillis() {
- return millis;
+ return this.millis;
}
public void setMillis(Long millis) {
@@ -283,48 +384,80 @@ public class DateFormattingTests {
}
@DateTimeFormat(style="S-")
- public Long getMillisAnnotated() {
- return millisAnnotated;
+ public Long getStyleMillis() {
+ return this.styleMillis;
+ }
+
+ public void setStyleMillis(@DateTimeFormat(style="S-") Long styleMillis) {
+ this.styleMillis = styleMillis;
+ }
+
+ public Calendar getStyleCalendar() {
+ return this.styleCalendar;
+ }
+
+ public void setStyleCalendar(Calendar styleCalendar) {
+ this.styleCalendar = styleCalendar;
+ }
+
+ public Calendar getStyleCalendarWithFallbackPatterns() {
+ return this.styleCalendarWithFallbackPatterns;
+ }
+
+ public void setStyleCalendarWithFallbackPatterns(Calendar styleCalendarWithFallbackPatterns) {
+ this.styleCalendarWithFallbackPatterns = styleCalendarWithFallbackPatterns;
+ }
+
+ public Date getStyleDate() {
+ return this.styleDate;
}
- public void setMillisAnnotated(@DateTimeFormat(style="S-") Long millisAnnotated) {
- this.millisAnnotated = millisAnnotated;
+ public void setStyleDate(Date styleDate) {
+ this.styleDate = styleDate;
}
- public Calendar getCalendarAnnotated() {
- return calendarAnnotated;
+ public Date getStyleDateWithFallbackPatterns() {
+ return this.styleDateWithFallbackPatterns;
}
- public void setCalendarAnnotated(Calendar calendarAnnotated) {
- this.calendarAnnotated = calendarAnnotated;
+ public void setStyleDateWithFallbackPatterns(Date styleDateWithFallbackPatterns) {
+ this.styleDateWithFallbackPatterns = styleDateWithFallbackPatterns;
}
- public Date getDateAnnotated() {
- return dateAnnotated;
+ public Date getPatternDate() {
+ return this.patternDate;
}
- public void setDateAnnotated(Date dateAnnotated) {
- this.dateAnnotated = dateAnnotated;
+ public void setPatternDate(Date patternDate) {
+ this.patternDate = patternDate;
}
- public Date getDateAnnotatedPattern() {
- return dateAnnotatedPattern;
+ public Date getPatternDateWithFallbackPatterns() {
+ return this.patternDateWithFallbackPatterns;
}
- public void setDateAnnotatedPattern(Date dateAnnotatedPattern) {
- this.dateAnnotatedPattern = dateAnnotatedPattern;
+ public void setPatternDateWithFallbackPatterns(Date patternDateWithFallbackPatterns) {
+ this.patternDateWithFallbackPatterns = patternDateWithFallbackPatterns;
}
public Date getIsoDate() {
- return isoDate;
+ return this.isoDate;
}
public void setIsoDate(Date isoDate) {
this.isoDate = isoDate;
}
+ public Date getIsoDateWithFallbackPatterns() {
+ return this.isoDateWithFallbackPatterns;
+ }
+
+ public void setIsoDateWithFallbackPatterns(Date isoDateWithFallbackPatterns) {
+ this.isoDateWithFallbackPatterns = isoDateWithFallbackPatterns;
+ }
+
public Date getIsoTime() {
- return isoTime;
+ return this.isoTime;
}
public void setIsoTime(Date isoTime) {
@@ -332,7 +465,7 @@ public class DateFormattingTests {
}
public Date getIsoDateTime() {
- return isoDateTime;
+ return this.isoDateTime;
}
public void setIsoDateTime(Date isoDateTime) {
@@ -340,7 +473,7 @@ public class DateFormattingTests {
}
public List getChildren() {
- return children;
+ return this.children;
}
}
diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
index 23f640cb94..105da3d2ae 100644
--- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
+++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Date;
@@ -38,15 +39,22 @@ import java.util.TimeZone;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.TypeMismatchException;
import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionService;
+import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
+import org.springframework.validation.FieldError;
import static org.assertj.core.api.Assertions.assertThat;
@@ -54,22 +62,22 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Keith Donald
* @author Juergen Hoeller
* @author Phillip Webb
+ * @author Sam Brannen
*/
-public class DateTimeFormattingTests {
+class DateTimeFormattingTests {
- private FormattingConversionService conversionService;
+ private final FormattingConversionService conversionService = new FormattingConversionService();
private DataBinder binder;
@BeforeEach
- public void setup() {
+ void setup() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
setup(registrar);
}
private void setup(DateTimeFormatterRegistrar registrar) {
- conversionService = new FormattingConversionService();
DefaultConversionService.addDefaultConverters(conversionService);
registrar.registerFormatters(conversionService);
@@ -85,14 +93,14 @@ public class DateTimeFormattingTests {
}
@AfterEach
- public void cleanup() {
+ void cleanup() {
LocaleContextHolder.setLocale(null);
DateTimeContextHolder.setDateTimeContext(null);
}
@Test
- public void testBindLocalDate() {
+ void testBindLocalDate() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "10/31/09");
binder.bind(propertyValues);
@@ -101,7 +109,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateWithSpecificStyle() {
+ void testBindLocalDateWithSpecificStyle() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateStyle(FormatStyle.LONG);
setup(registrar);
@@ -113,7 +121,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateWithSpecificFormatter() {
+ void testBindLocalDateWithSpecificFormatter() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
setup(registrar);
@@ -125,7 +133,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateArray() {
+ void testBindLocalDateArray() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", new String[] {"10/31/09"});
binder.bind(propertyValues);
@@ -133,54 +141,54 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateAnnotated() {
+ void testBindLocalDateAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDateAnnotated", "Oct 31, 2009");
+ propertyValues.add("styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("localDateAnnotated")).isEqualTo("Oct 31, 2009");
+ assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@Test
- public void testBindLocalDateAnnotatedWithError() {
+ void testBindLocalDateAnnotatedWithError() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDateAnnotated", "Oct -31, 2009");
+ propertyValues.add("styleLocalDate", "Oct -31, 2009");
binder.bind(propertyValues);
- assertThat(binder.getBindingResult().getFieldErrorCount("localDateAnnotated")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("localDateAnnotated")).isEqualTo("Oct -31, 2009");
+ assertThat(binder.getBindingResult().getFieldErrorCount("styleLocalDate")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct -31, 2009");
}
@Test
- public void testBindNestedLocalDateAnnotated() {
+ void testBindNestedLocalDateAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("children[0].localDateAnnotated", "Oct 31, 2009");
+ propertyValues.add("children[0].styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("children[0].localDateAnnotated")).isEqualTo("Oct 31, 2009");
+ assertThat(binder.getBindingResult().getFieldValue("children[0].styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@Test
- public void testBindLocalDateAnnotatedWithDirectFieldAccess() {
+ void testBindLocalDateAnnotatedWithDirectFieldAccess() {
binder.initDirectFieldAccess();
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDateAnnotated", "Oct 31, 2009");
+ propertyValues.add("styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("localDateAnnotated")).isEqualTo("Oct 31, 2009");
+ assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@Test
- public void testBindLocalDateAnnotatedWithDirectFieldAccessAndError() {
+ void testBindLocalDateAnnotatedWithDirectFieldAccessAndError() {
binder.initDirectFieldAccess();
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDateAnnotated", "Oct -31, 2009");
+ propertyValues.add("styleLocalDate", "Oct -31, 2009");
binder.bind(propertyValues);
- assertThat(binder.getBindingResult().getFieldErrorCount("localDateAnnotated")).isEqualTo(1);
- assertThat(binder.getBindingResult().getFieldValue("localDateAnnotated")).isEqualTo("Oct -31, 2009");
+ assertThat(binder.getBindingResult().getFieldErrorCount("styleLocalDate")).isEqualTo(1);
+ assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct -31, 2009");
}
@Test
- public void testBindLocalDateFromJavaUtilCalendar() {
+ void testBindLocalDateFromJavaUtilCalendar() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", new GregorianCalendar(2009, 9, 31, 0, 0));
binder.bind(propertyValues);
@@ -189,7 +197,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalTime() {
+ void testBindLocalTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "12:00 PM");
binder.bind(propertyValues);
@@ -198,7 +206,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalTimeWithSpecificStyle() {
+ void testBindLocalTimeWithSpecificStyle() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setTimeStyle(FormatStyle.MEDIUM);
setup(registrar);
@@ -210,7 +218,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalTimeWithSpecificFormatter() {
+ void testBindLocalTimeWithSpecificFormatter() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setTimeFormatter(DateTimeFormatter.ofPattern("HHmmss"));
setup(registrar);
@@ -222,16 +230,16 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalTimeAnnotated() {
+ void testBindLocalTimeAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localTimeAnnotated", "12:00:00 PM");
+ propertyValues.add("styleLocalTime", "12:00:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("localTimeAnnotated")).isEqualTo("12:00:00 PM");
+ assertThat(binder.getBindingResult().getFieldValue("styleLocalTime")).isEqualTo("12:00:00 PM");
}
@Test
- public void testBindLocalTimeFromJavaUtilCalendar() {
+ void testBindLocalTimeFromJavaUtilCalendar() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", new GregorianCalendar(1970, 0, 0, 12, 0));
binder.bind(propertyValues);
@@ -240,7 +248,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateTime() {
+ void testBindLocalDateTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
binder.bind(propertyValues);
@@ -251,18 +259,18 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindLocalDateTimeAnnotated() {
+ void testBindLocalDateTimeAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("localDateTimeAnnotated", LocalDateTime.of(2009, 10, 31, 12, 0));
+ propertyValues.add("styleLocalDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- String value = binder.getBindingResult().getFieldValue("localDateTimeAnnotated").toString();
+ String value = binder.getBindingResult().getFieldValue("styleLocalDateTime").toString();
assertThat(value.startsWith("Oct 31, 2009")).isTrue();
assertThat(value.endsWith("12:00:00 PM")).isTrue();
}
@Test
- public void testBindLocalDateTimeFromJavaUtilCalendar() {
+ void testBindLocalDateTimeFromJavaUtilCalendar() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", new GregorianCalendar(2009, 9, 31, 12, 0));
binder.bind(propertyValues);
@@ -273,7 +281,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindDateTimeWithSpecificStyle() {
+ void testBindDateTimeWithSpecificStyle() {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateTimeStyle(FormatStyle.MEDIUM);
setup(registrar);
@@ -287,69 +295,69 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindDateTimeAnnotatedPattern() {
+ void testBindPatternLocalDateTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateTimeAnnotatedPattern", "10/31/09 12:00 PM");
+ propertyValues.add("patternLocalDateTime", "10/31/09 12:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("dateTimeAnnotatedPattern")).isEqualTo("10/31/09 12:00 PM");
+ assertThat(binder.getBindingResult().getFieldValue("patternLocalDateTime")).isEqualTo("10/31/09 12:00 PM");
}
@Test
- public void testBindDateTimeOverflow() {
+ void testBindDateTimeOverflow() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("dateTimeAnnotatedPattern", "02/29/09 12:00 PM");
+ propertyValues.add("patternLocalDateTime", "02/29/09 12:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(1);
}
@Test
- public void testBindISODate() {
+ void testBindISODate() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("isoDate", "2009-10-31");
+ propertyValues.add("isoLocalDate", "2009-10-31");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("isoDate")).isEqualTo("2009-10-31");
+ assertThat(binder.getBindingResult().getFieldValue("isoLocalDate")).isEqualTo("2009-10-31");
}
@Test
- public void testBindISOTime() {
+ void testBindISOTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("isoTime", "12:00:00");
+ propertyValues.add("isoLocalTime", "12:00:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("isoTime")).isEqualTo("12:00:00");
+ assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
}
@Test
- public void testBindISOTimeWithZone() {
+ void testBindISOTimeWithZone() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("isoTime", "12:00:00.000-05:00");
+ propertyValues.add("isoLocalTime", "12:00:00.000-05:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("isoTime")).isEqualTo("12:00:00");
+ assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
}
@Test
- public void testBindISODateTime() {
+ void testBindISODateTime() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("isoDateTime", "2009-10-31T12:00:00");
+ propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("isoDateTime")).isEqualTo("2009-10-31T12:00:00");
+ assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
}
@Test
- public void testBindISODateTimeWithZone() {
+ void testBindISODateTimeWithZone() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
- propertyValues.add("isoDateTime", "2009-10-31T12:00:00.000Z");
+ propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00.000Z");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
- assertThat(binder.getBindingResult().getFieldValue("isoDateTime")).isEqualTo("2009-10-31T12:00:00");
+ assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
}
@Test
- public void testBindInstant() {
+ void testBindInstant() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("instant", "2009-10-31T12:00:00.000Z");
binder.bind(propertyValues);
@@ -359,7 +367,7 @@ public class DateTimeFormattingTests {
@Test
@SuppressWarnings("deprecation")
- public void testBindInstantFromJavaUtilDate() {
+ void testBindInstantFromJavaUtilDate() {
TimeZone defaultZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
try {
@@ -375,7 +383,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindPeriod() {
+ void testBindPeriod() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("period", "P6Y3M1D");
binder.bind(propertyValues);
@@ -384,7 +392,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindDuration() {
+ void testBindDuration() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("duration", "PT8H6M12.345S");
binder.bind(propertyValues);
@@ -393,7 +401,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindYear() {
+ void testBindYear() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("year", "2007");
binder.bind(propertyValues);
@@ -402,7 +410,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindMonth() {
+ void testBindMonth() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("month", "JULY");
binder.bind(propertyValues);
@@ -411,7 +419,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindMonthInAnyCase() {
+ void testBindMonthInAnyCase() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("month", "July");
binder.bind(propertyValues);
@@ -420,7 +428,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindYearMonth() {
+ void testBindYearMonth() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("yearMonth", "2007-12");
binder.bind(propertyValues);
@@ -429,7 +437,7 @@ public class DateTimeFormattingTests {
}
@Test
- public void testBindMonthDay() {
+ void testBindMonthDay() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("monthDay", "--12-03");
binder.bind(propertyValues);
@@ -437,35 +445,125 @@ public class DateTimeFormattingTests {
assertThat(binder.getBindingResult().getFieldValue("monthDay").toString().equals("--12-03")).isTrue();
}
+ @Nested
+ class FallbackPatternTests {
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void styleLocalDate(String propertyValue) {
+ String propertyName = "styleLocalDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("3/2/21");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02", "2021.03.02", "20210302", "3/2/21"})
+ void patternLocalDate(String propertyValue) {
+ String propertyName = "patternLocalDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"12:00:00 PM", "12:00:00", "12:00"})
+ void styleLocalTime(String propertyValue) {
+ String propertyName = "styleLocalTimeWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("12:00:00 PM");
+ }
+
+ @ParameterizedTest(name = "input date: {0}")
+ @ValueSource(strings = {"2021-03-02T12:00:00", "2021-03-02 12:00:00", "3/2/21 12:00"})
+ void isoLocalDateTime(String propertyValue) {
+ String propertyName = "isoLocalDateTimeWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(0);
+ assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02T12:00:00");
+ }
+
+ @Test
+ void patternLocalDateWithUnsupportedPattern() {
+ String propertyValue = "210302";
+ String propertyName = "patternLocalDateWithFallbackPatterns";
+ MutablePropertyValues propertyValues = new MutablePropertyValues();
+ propertyValues.add(propertyName, propertyValue);
+ binder.bind(propertyValues);
+ BindingResult bindingResult = binder.getBindingResult();
+ assertThat(bindingResult.getErrorCount()).isEqualTo(1);
+ FieldError fieldError = bindingResult.getFieldError(propertyName);
+ assertThat(fieldError.unwrap(TypeMismatchException.class))
+ .hasMessageContaining("for property 'patternLocalDateWithFallbackPatterns'")
+ .hasCauseInstanceOf(ConversionFailedException.class).getCause()
+ .hasMessageContaining("for value '210302'")
+ .hasCauseInstanceOf(IllegalArgumentException.class).getCause()
+ .hasMessageContaining("Parse attempt failed for value [210302]")
+ .hasCauseInstanceOf(DateTimeParseException.class).getCause()
+ // Unable to parse date time value "210302" using configuration from
+ // @org.springframework.format.annotation.DateTimeFormat(
+ // pattern=yyyy-MM-dd, style=SS, iso=NONE, fallbackPatterns=[M/d/yy, yyyyMMdd, yyyy.MM.dd])
+ .hasMessageContainingAll(
+ "Unable to parse date time value \"210302\" using configuration from",
+ "@org.springframework.format.annotation.DateTimeFormat",
+ "yyyy-MM-dd", "M/d/yy", "yyyyMMdd", "yyyy.MM.dd");
+ }
+}
+
public static class DateTimeBean {
private LocalDate localDate;
@DateTimeFormat(style = "M-")
- private LocalDate localDateAnnotated;
+ private LocalDate styleLocalDate;
+
+ @DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
+ private LocalDate styleLocalDateWithFallbackPatterns;
+
+ @DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
+ private LocalDate patternLocalDateWithFallbackPatterns;
private LocalTime localTime;
@DateTimeFormat(style = "-M")
- private LocalTime localTimeAnnotated;
+ private LocalTime styleLocalTime;
+
+ @DateTimeFormat(style = "-M", fallbackPatterns = { "HH:mm:ss", "HH:mm"})
+ private LocalTime styleLocalTimeWithFallbackPatterns;
private LocalDateTime localDateTime;
@DateTimeFormat(style = "MM")
- private LocalDateTime localDateTimeAnnotated;
+ private LocalDateTime styleLocalDateTime;
@DateTimeFormat(pattern = "M/d/yy h:mm a")
- private LocalDateTime dateTimeAnnotatedPattern;
+ private LocalDateTime patternLocalDateTime;
@DateTimeFormat(iso = ISO.DATE)
- private LocalDate isoDate;
+ private LocalDate isoLocalDate;
@DateTimeFormat(iso = ISO.TIME)
- private LocalTime isoTime;
+ private LocalTime isoLocalTime;
@DateTimeFormat(iso = ISO.DATE_TIME)
- private LocalDateTime isoDateTime;
+ private LocalDateTime isoLocalDateTime;
+
+ @DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = { "yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
+ private LocalDateTime isoLocalDateTimeWithFallbackPatterns;
private Instant instant;
@@ -483,88 +581,120 @@ public class DateTimeFormattingTests {
private final List children = new ArrayList<>();
+
public LocalDate getLocalDate() {
- return localDate;
+ return this.localDate;
}
public void setLocalDate(LocalDate localDate) {
this.localDate = localDate;
}
- public LocalDate getLocalDateAnnotated() {
- return localDateAnnotated;
+ public LocalDate getStyleLocalDate() {
+ return this.styleLocalDate;
}
- public void setLocalDateAnnotated(LocalDate localDateAnnotated) {
- this.localDateAnnotated = localDateAnnotated;
+ public void setStyleLocalDate(LocalDate styleLocalDate) {
+ this.styleLocalDate = styleLocalDate;
+ }
+
+ public LocalDate getStyleLocalDateWithFallbackPatterns() {
+ return this.styleLocalDateWithFallbackPatterns;
+ }
+
+ public void setStyleLocalDateWithFallbackPatterns(LocalDate styleLocalDateWithFallbackPatterns) {
+ this.styleLocalDateWithFallbackPatterns = styleLocalDateWithFallbackPatterns;
+ }
+ public LocalDate getPatternLocalDateWithFallbackPatterns() {
+ return this.patternLocalDateWithFallbackPatterns;
+ }
+
+ public void setPatternLocalDateWithFallbackPatterns(LocalDate patternLocalDateWithFallbackPatterns) {
+ this.patternLocalDateWithFallbackPatterns = patternLocalDateWithFallbackPatterns;
}
public LocalTime getLocalTime() {
- return localTime;
+ return this.localTime;
}
public void setLocalTime(LocalTime localTime) {
this.localTime = localTime;
}
- public LocalTime getLocalTimeAnnotated() {
- return localTimeAnnotated;
+ public LocalTime getStyleLocalTime() {
+ return this.styleLocalTime;
+ }
+
+ public void setStyleLocalTime(LocalTime styleLocalTime) {
+ this.styleLocalTime = styleLocalTime;
+ }
+
+ public LocalTime getStyleLocalTimeWithFallbackPatterns() {
+ return this.styleLocalTimeWithFallbackPatterns;
}
- public void setLocalTimeAnnotated(LocalTime localTimeAnnotated) {
- this.localTimeAnnotated = localTimeAnnotated;
+ public void setStyleLocalTimeWithFallbackPatterns(LocalTime styleLocalTimeWithFallbackPatterns) {
+ this.styleLocalTimeWithFallbackPatterns = styleLocalTimeWithFallbackPatterns;
}
public LocalDateTime getLocalDateTime() {
- return localDateTime;
+ return this.localDateTime;
}
public void setLocalDateTime(LocalDateTime localDateTime) {
this.localDateTime = localDateTime;
}
- public LocalDateTime getLocalDateTimeAnnotated() {
- return localDateTimeAnnotated;
+ public LocalDateTime getStyleLocalDateTime() {
+ return this.styleLocalDateTime;
+ }
+
+ public void setStyleLocalDateTime(LocalDateTime styleLocalDateTime) {
+ this.styleLocalDateTime = styleLocalDateTime;
+ }
+
+ public LocalDateTime getPatternLocalDateTime() {
+ return this.patternLocalDateTime;
}
- public void setLocalDateTimeAnnotated(LocalDateTime localDateTimeAnnotated) {
- this.localDateTimeAnnotated = localDateTimeAnnotated;
+ public void setPatternLocalDateTime(LocalDateTime patternLocalDateTime) {
+ this.patternLocalDateTime = patternLocalDateTime;
}
- public LocalDateTime getDateTimeAnnotatedPattern() {
- return dateTimeAnnotatedPattern;
+ public LocalDate getIsoLocalDate() {
+ return this.isoLocalDate;
}
- public void setDateTimeAnnotatedPattern(LocalDateTime dateTimeAnnotatedPattern) {
- this.dateTimeAnnotatedPattern = dateTimeAnnotatedPattern;
+ public void setIsoLocalDate(LocalDate isoLocalDate) {
+ this.isoLocalDate = isoLocalDate;
}
- public LocalDate getIsoDate() {
- return isoDate;
+ public LocalTime getIsoLocalTime() {
+ return this.isoLocalTime;
}
- public void setIsoDate(LocalDate isoDate) {
- this.isoDate = isoDate;
+ public void setIsoLocalTime(LocalTime isoLocalTime) {
+ this.isoLocalTime = isoLocalTime;
}
- public LocalTime getIsoTime() {
- return isoTime;
+ public LocalDateTime getIsoLocalDateTime() {
+ return this.isoLocalDateTime;
}
- public void setIsoTime(LocalTime isoTime) {
- this.isoTime = isoTime;
+ public void setIsoLocalDateTime(LocalDateTime isoLocalDateTime) {
+ this.isoLocalDateTime = isoLocalDateTime;
}
- public LocalDateTime getIsoDateTime() {
- return isoDateTime;
+ public LocalDateTime getIsoLocalDateTimeWithFallbackPatterns() {
+ return this.isoLocalDateTimeWithFallbackPatterns;
}
- public void setIsoDateTime(LocalDateTime isoDateTime) {
- this.isoDateTime = isoDateTime;
+ public void setIsoLocalDateTimeWithFallbackPatterns(LocalDateTime isoLocalDateTimeWithFallbackPatterns) {
+ this.isoLocalDateTimeWithFallbackPatterns = isoLocalDateTimeWithFallbackPatterns;
}
public Instant getInstant() {
- return instant;
+ return this.instant;
}
public void setInstant(Instant instant) {
@@ -572,7 +702,7 @@ public class DateTimeFormattingTests {
}
public Period getPeriod() {
- return period;
+ return this.period;
}
public void setPeriod(Period period) {
@@ -580,7 +710,7 @@ public class DateTimeFormattingTests {
}
public Duration getDuration() {
- return duration;
+ return this.duration;
}
public void setDuration(Duration duration) {
@@ -588,7 +718,7 @@ public class DateTimeFormattingTests {
}
public Year getYear() {
- return year;
+ return this.year;
}
public void setYear(Year year) {
@@ -596,7 +726,7 @@ public class DateTimeFormattingTests {
}
public Month getMonth() {
- return month;
+ return this.month;
}
public void setMonth(Month month) {
@@ -604,7 +734,7 @@ public class DateTimeFormattingTests {
}
public YearMonth getYearMonth() {
- return yearMonth;
+ return this.yearMonth;
}
public void setYearMonth(YearMonth yearMonth) {
@@ -612,7 +742,7 @@ public class DateTimeFormattingTests {
}
public MonthDay getMonthDay() {
- return monthDay;
+ return this.monthDay;
}
public void setMonthDay(MonthDay monthDay) {
@@ -620,7 +750,7 @@ public class DateTimeFormattingTests {
}
public List getChildren() {
- return children;
+ return this.children;
}
}