From 69b51cd64d774ee0c08d5f8e2db3f6aa9e3a3aa8 Mon Sep 17 00:00:00 2001 From: Daniel Knittl-Frank Date: Mon, 28 Oct 2019 21:27:32 +0100 Subject: [PATCH 1/2] Enable users to provide custom time and datetime formats Extend WebFlux and WebMvc properties with timeFormat and dateTimeFormat properties to allow users to customize format of LocalTime and LocalDateTime instances. See gh-18772 --- .../web/format/WebConversionService.java | 36 +++++++++++++++++-- .../reactive/WebFluxAutoConfiguration.java | 3 +- .../web/reactive/WebFluxProperties.java | 26 ++++++++++++++ .../web/servlet/WebMvcAutoConfiguration.java | 3 +- .../web/servlet/WebMvcProperties.java | 26 ++++++++++++++ .../web/format/WebConversionServiceTests.java | 20 +++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java index cb737da3b7..cd82405b05 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java @@ -48,15 +48,32 @@ public class WebConversionService extends DefaultFormattingConversionService { private final String dateFormat; + private final String timeFormat; + + private final String dateTimeFormat; + /** * Create a new WebConversionService that configures formatters with the provided date * format, or register the default ones if no custom format is provided. * @param dateFormat the custom date format to use for date conversions */ public WebConversionService(String dateFormat) { + this(dateFormat, null, null); + } + + /** + * Create a new WebConversionService that configures formatters with the provided date + * and time formats, or register the default ones if no custom formats are provided. + * @param dateFormat the custom date format to use for date conversions + * @param timeFormat the custom time format to use for time conversions + * @param dateTimeFormat the custom datetime format to use for datetime conversions + */ + public WebConversionService(String dateFormat, String timeFormat, String dateTimeFormat) { super(false); - this.dateFormat = StringUtils.hasText(dateFormat) ? dateFormat : null; - if (this.dateFormat != null) { + this.dateFormat = getNonEmptyFormat(dateFormat); + this.timeFormat = getNonEmptyFormat(timeFormat); + this.dateTimeFormat = getNonEmptyFormat(dateTimeFormat); + if (this.dateFormat != null || this.timeFormat != null || this.dateTimeFormat != null) { addFormatters(); } else { @@ -81,6 +98,17 @@ public class WebConversionService extends DefaultFormattingConversionService { dateTime.setDateFormatter( DateTimeFormatter.ofPattern(this.dateFormat).withResolverStyle(ResolverStyle.SMART)); } + + if (this.timeFormat != null) { + dateTime.setTimeFormatter( + DateTimeFormatter.ofPattern(this.timeFormat).withResolverStyle(ResolverStyle.SMART)); + } + + if (this.dateTimeFormat != null) { + dateTime.setDateTimeFormatter( + DateTimeFormatter.ofPattern(this.dateTimeFormat).withResolverStyle(ResolverStyle.SMART)); + } + dateTime.registerFormatters(this); } @@ -93,4 +121,8 @@ public class WebConversionService extends DefaultFormattingConversionService { dateFormatterRegistrar.registerFormatters(this); } + private static String getNonEmptyFormat(final String format) { + return StringUtils.hasText(format) ? format : null; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 18a25fc81e..dab1d88e0d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -203,7 +203,8 @@ public class WebFluxAutoConfiguration { @Bean @Override public FormattingConversionService webFluxConversionService() { - WebConversionService conversionService = new WebConversionService(this.webFluxProperties.getDateFormat()); + WebConversionService conversionService = new WebConversionService(this.webFluxProperties.getDateFormat(), + this.webFluxProperties.getTimeFormat(), this.webFluxProperties.getDateTimeFormat()); addFormatters(conversionService); return conversionService; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java index bb4293f61f..88bd706e97 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java @@ -38,6 +38,16 @@ public class WebFluxProperties { */ private String dateFormat; + /** + * Time format to use. For instance, `HH:mm:ss`. + */ + private String timeFormat; + + /** + * Datetime format to use. For instance, `yyyy-MM-dd HH:mm:ss`. + */ + private String dateTimeFormat; + /** * Path pattern used for static resources. */ @@ -72,6 +82,22 @@ public class WebFluxProperties { this.dateFormat = dateFormat; } + public String getTimeFormat() { + return this.timeFormat; + } + + public void setTimeFormat(final String timeFormat) { + this.timeFormat = timeFormat; + } + + public String getDateTimeFormat() { + return this.dateTimeFormat; + } + + public void setDateTimeFormat(final String dateTimeFormat) { + this.dateTimeFormat = dateTimeFormat; + } + public String getStaticPathPattern() { return this.staticPathPattern; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 1275b25e97..bb9e29a268 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -427,7 +427,8 @@ public class WebMvcAutoConfiguration { @Bean @Override public FormattingConversionService mvcConversionService() { - WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat()); + WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat(), + this.mvcProperties.getTimeFormat(), this.mvcProperties.getDateTimeFormat()); addFormatters(conversionService); return conversionService; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java index 5e4581ac68..db7f55ebfe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java @@ -61,6 +61,16 @@ public class WebMvcProperties { */ private String dateFormat; + /** + * Time format to use. For instance, `HH:mm:ss`. + */ + private String timeFormat; + + /** + * Datetime format to use. For instance, `yyyy-MM-dd HH:mm:ss`. + */ + private String dateTimeFormat; + /** * Whether to dispatch TRACE requests to the FrameworkServlet doService method. */ @@ -147,6 +157,22 @@ public class WebMvcProperties { this.dateFormat = dateFormat; } + public String getTimeFormat() { + return this.timeFormat; + } + + public void setTimeFormat(final String timeFormat) { + this.timeFormat = timeFormat; + } + + public String getDateTimeFormat() { + return this.dateTimeFormat; + } + + public void setDateTimeFormat(final String dateTimeFormat) { + this.dateTimeFormat = dateTimeFormat; + } + public boolean isIgnoreDefaultModelOnRedirect() { return this.ignoreDefaultModelOnRedirect; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java index dde2a4e7b1..e72f575b93 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java @@ -42,11 +42,31 @@ class WebConversionServiceTests { customDateFormat(java.time.LocalDate.of(2018, 1, 1)); } + @Test + void customTimeFormatWithJavaTime() { + customTimeFormat(java.time.LocalTime.of(13, 37, 42)); + } + + @Test + void customDateTimeFormatWithJavaTime() { + customDateTimeFormat(java.time.LocalDateTime.of(2019, 10, 28, 13, 37, 42)); + } + private void customDateFormat(Object input) { WebConversionService conversionService = new WebConversionService("dd*MM*yyyy"); assertThat(conversionService.convert(input, String.class)).isEqualTo("01*01*2018"); } + private void customTimeFormat(Object input) { + WebConversionService conversionService = new WebConversionService(null, "HH*mm*ss", null); + assertThat(conversionService.convert(input, String.class)).isEqualTo("13*37*42"); + } + + private void customDateTimeFormat(Object input) { + WebConversionService conversionService = new WebConversionService(null, null, "dd*MM*yyyy HH*mm*ss"); + assertThat(conversionService.convert(input, String.class)).isEqualTo("28*10*2019 13*37*42"); + } + @Test void convertFromStringToDate() { WebConversionService conversionService = new WebConversionService("yyyy-MM-dd"); From 95e5d4e045f244a7a9bff0e1933cf430c5a67ebb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 28 Apr 2020 12:15:06 +0100 Subject: [PATCH 2/2] Polish "Enable users to provide custom time and datetime formats" See gh-18772 --- .../web/format/DateTimeFormatters.java | 96 +++++++++++++++++++ .../web/format/WebConversionService.java | 79 +++++++-------- .../reactive/WebFluxAutoConfiguration.java | 7 +- .../web/reactive/WebFluxProperties.java | 82 ++++++++++------ .../web/servlet/WebMvcAutoConfiguration.java | 7 +- .../web/servlet/WebMvcProperties.java | 81 ++++++++++------ .../web/format/WebConversionServiceTests.java | 59 ++++++++---- .../WebFluxAutoConfigurationTests.java | 55 ++++++++++- .../servlet/WebMvcAutoConfigurationTests.java | 55 ++++++++++- 9 files changed, 391 insertions(+), 130 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/DateTimeFormatters.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/DateTimeFormatters.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/DateTimeFormatters.java new file mode 100644 index 0000000000..8941094224 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/DateTimeFormatters.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2020 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.boot.autoconfigure.web.format; + +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import org.springframework.util.StringUtils; + +/** + * {@link DateTimeFormatter Formatters} for dates, times, and date-times. + * + * @author Andy Wilkinson + * @since 2.3.0 + */ +public class DateTimeFormatters { + + private DateTimeFormatter dateFormatter; + + private String datePattern; + + private DateTimeFormatter timeFormatter; + + private DateTimeFormatter dateTimeFormatter; + + /** + * Configures the date format using the given {@code pattern}. + * @param pattern the pattern for formatting dates + * @return {@code this} for chained method invocation + */ + public DateTimeFormatters dateFormat(String pattern) { + this.dateFormatter = formatter(pattern); + this.datePattern = pattern; + return this; + } + + /** + * Configures the time format using the given {@code pattern}. + * @param pattern the pattern for formatting times + * @return {@code this} for chained method invocation + */ + public DateTimeFormatters timeFormat(String pattern) { + this.timeFormatter = formatter(pattern); + return this; + } + + /** + * Configures the date-time format using the given {@code pattern}. + * @param pattern the pattern for formatting date-times + * @return {@code this} for chained method invocation + */ + public DateTimeFormatters dateTimeFormat(String pattern) { + this.dateTimeFormatter = formatter(pattern); + return this; + } + + DateTimeFormatter getDateFormatter() { + return this.dateFormatter; + } + + String getDatePattern() { + return this.datePattern; + } + + DateTimeFormatter getTimeFormatter() { + return this.timeFormatter; + } + + DateTimeFormatter getDateTimeFormatter() { + return this.dateTimeFormatter; + } + + boolean isCustomized() { + return this.dateFormatter != null || this.timeFormatter != null || this.dateTimeFormatter != null; + } + + private static DateTimeFormatter formatter(String pattern) { + return StringUtils.hasText(pattern) + ? DateTimeFormatter.ofPattern(pattern).withResolverStyle(ResolverStyle.SMART) : null; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java index cd82405b05..2aa3c91dcf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/format/WebConversionService.java @@ -17,7 +17,8 @@ package org.springframework.boot.autoconfigure.web.format; import java.time.format.DateTimeFormatter; -import java.time.format.ResolverStyle; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.springframework.format.datetime.DateFormatter; import org.springframework.format.datetime.DateFormatterRegistrar; @@ -28,7 +29,6 @@ import org.springframework.format.number.money.Jsr354NumberFormatAnnotationForma import org.springframework.format.number.money.MonetaryAmountFormatter; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * {@link org.springframework.format.support.FormattingConversionService} dedicated to web @@ -46,83 +46,70 @@ public class WebConversionService extends DefaultFormattingConversionService { private static final boolean JSR_354_PRESENT = ClassUtils.isPresent("javax.money.MonetaryAmount", WebConversionService.class.getClassLoader()); - private final String dateFormat; - - private final String timeFormat; - - private final String dateTimeFormat; - /** * Create a new WebConversionService that configures formatters with the provided date * format, or register the default ones if no custom format is provided. * @param dateFormat the custom date format to use for date conversions + * @deprecated since 2.3.0 in favor of + * {@link #WebConversionService(DateTimeFormatters)} */ + @Deprecated public WebConversionService(String dateFormat) { - this(dateFormat, null, null); + this(new DateTimeFormatters().dateFormat(dateFormat)); } /** - * Create a new WebConversionService that configures formatters with the provided date - * and time formats, or register the default ones if no custom formats are provided. - * @param dateFormat the custom date format to use for date conversions - * @param timeFormat the custom time format to use for time conversions - * @param dateTimeFormat the custom datetime format to use for datetime conversions + * Create a new WebConversionService that configures formatters with the provided + * date, time, and date-time formats, or registers the default if no custom format is + * provided. + * @param dateTimeFormatters the formatters to use for date, time, and date-time + * formatting + * @since 2.3.0 */ - public WebConversionService(String dateFormat, String timeFormat, String dateTimeFormat) { + public WebConversionService(DateTimeFormatters dateTimeFormatters) { super(false); - this.dateFormat = getNonEmptyFormat(dateFormat); - this.timeFormat = getNonEmptyFormat(timeFormat); - this.dateTimeFormat = getNonEmptyFormat(dateTimeFormat); - if (this.dateFormat != null || this.timeFormat != null || this.dateTimeFormat != null) { - addFormatters(); + if (dateTimeFormatters.isCustomized()) { + addFormatters(dateTimeFormatters); } else { addDefaultFormatters(this); } } - private void addFormatters() { + private void addFormatters(DateTimeFormatters dateTimeFormatters) { addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); if (JSR_354_PRESENT) { addFormatter(new CurrencyUnitFormatter()); addFormatter(new MonetaryAmountFormatter()); addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory()); } - registerJsr310(); - registerJavaDate(); + registerJsr310(dateTimeFormatters); + registerJavaDate(dateTimeFormatters); } - private void registerJsr310() { + private void registerJsr310(DateTimeFormatters dateTimeFormatters) { DateTimeFormatterRegistrar dateTime = new DateTimeFormatterRegistrar(); - if (this.dateFormat != null) { - dateTime.setDateFormatter( - DateTimeFormatter.ofPattern(this.dateFormat).withResolverStyle(ResolverStyle.SMART)); - } - - if (this.timeFormat != null) { - dateTime.setTimeFormatter( - DateTimeFormatter.ofPattern(this.timeFormat).withResolverStyle(ResolverStyle.SMART)); - } - - if (this.dateTimeFormat != null) { - dateTime.setDateTimeFormatter( - DateTimeFormatter.ofPattern(this.dateTimeFormat).withResolverStyle(ResolverStyle.SMART)); - } - + configure(dateTimeFormatters::getDateFormatter, dateTime::setDateFormatter); + configure(dateTimeFormatters::getTimeFormatter, dateTime::setTimeFormatter); + configure(dateTimeFormatters::getDateTimeFormatter, dateTime::setDateTimeFormatter); dateTime.registerFormatters(this); } - private void registerJavaDate() { + private void configure(Supplier supplier, Consumer consumer) { + DateTimeFormatter formatter = supplier.get(); + if (formatter != null) { + consumer.accept(formatter); + } + } + + private void registerJavaDate(DateTimeFormatters dateTimeFormatters) { DateFormatterRegistrar dateFormatterRegistrar = new DateFormatterRegistrar(); - if (this.dateFormat != null) { - DateFormatter dateFormatter = new DateFormatter(this.dateFormat); + String datePattern = dateTimeFormatters.getDatePattern(); + if (datePattern != null) { + DateFormatter dateFormatter = new DateFormatter(datePattern); dateFormatterRegistrar.setFormatter(dateFormatter); } dateFormatterRegistrar.registerFormatters(this); } - private static String getNonEmptyFormat(final String format) { - return StringUtils.hasText(format) ? format : null; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index dab1d88e0d..1907d5460b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -35,7 +35,9 @@ import org.springframework.boot.autoconfigure.validation.ValidationAutoConfigura import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ResourceProperties; +import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters; import org.springframework.boot.autoconfigure.web.format.WebConversionService; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Format; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.web.codec.CodecCustomizer; @@ -203,8 +205,9 @@ public class WebFluxAutoConfiguration { @Bean @Override public FormattingConversionService webFluxConversionService() { - WebConversionService conversionService = new WebConversionService(this.webFluxProperties.getDateFormat(), - this.webFluxProperties.getTimeFormat(), this.webFluxProperties.getDateTimeFormat()); + Format format = this.webFluxProperties.getFormat(); + WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() + .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java index 88bd706e97..b4a991232b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.web.reactive; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.util.StringUtils; /** @@ -33,20 +34,7 @@ public class WebFluxProperties { */ private String basePath; - /** - * Date format to use. For instance, `dd/MM/yyyy`. - */ - private String dateFormat; - - /** - * Time format to use. For instance, `HH:mm:ss`. - */ - private String timeFormat; - - /** - * Datetime format to use. For instance, `yyyy-MM-dd HH:mm:ss`. - */ - private String dateTimeFormat; + private final Format format = new Format(); /** * Path pattern used for static resources. @@ -74,28 +62,19 @@ public class WebFluxProperties { return candidate; } + @Deprecated + @DeprecatedConfigurationProperty(replacement = "spring.webflux.format.date") public String getDateFormat() { - return this.dateFormat; + return this.format.getDate(); } + @Deprecated public void setDateFormat(String dateFormat) { - this.dateFormat = dateFormat; + this.format.setDate(dateFormat); } - public String getTimeFormat() { - return this.timeFormat; - } - - public void setTimeFormat(final String timeFormat) { - this.timeFormat = timeFormat; - } - - public String getDateTimeFormat() { - return this.dateTimeFormat; - } - - public void setDateTimeFormat(final String dateTimeFormat) { - this.dateTimeFormat = dateTimeFormat; + public Format getFormat() { + return this.format; } public String getStaticPathPattern() { @@ -106,4 +85,47 @@ public class WebFluxProperties { this.staticPathPattern = staticPathPattern; } + public static class Format { + + /** + * Date format to use, for example `dd/MM/yyyy`. + */ + private String date; + + /** + * Time format to use, for example `HH:mm:ss`. + */ + private String time; + + /** + * Date-time format to use, for example `yyyy-MM-dd HH:mm:ss`. + */ + private String dateTime; + + public String getDate() { + return this.date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getTime() { + return this.time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getDateTime() { + return this.dateTime; + } + + public void setDateTime(String dateTime) { + this.dateTime = dateTime; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index bb9e29a268..062484c1fd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -51,7 +51,9 @@ import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy; +import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters; import org.springframework.boot.autoconfigure.web.format.WebConversionService; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; @@ -427,8 +429,9 @@ public class WebMvcAutoConfiguration { @Bean @Override public FormattingConversionService mvcConversionService() { - WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat(), - this.mvcProperties.getTimeFormat(), this.mvcProperties.getDateTimeFormat()); + Format format = this.mvcProperties.getFormat(); + WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() + .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java index db7f55ebfe..8f2aa4bd22 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java @@ -56,20 +56,7 @@ public class WebMvcProperties { */ private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER; - /** - * Date format to use. For instance, `dd/MM/yyyy`. - */ - private String dateFormat; - - /** - * Time format to use. For instance, `HH:mm:ss`. - */ - private String timeFormat; - - /** - * Datetime format to use. For instance, `yyyy-MM-dd HH:mm:ss`. - */ - private String dateTimeFormat; + private final Format format = new Format(); /** * Whether to dispatch TRACE requests to the FrameworkServlet doService method. @@ -149,28 +136,19 @@ public class WebMvcProperties { this.localeResolver = localeResolver; } + @Deprecated + @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date") public String getDateFormat() { - return this.dateFormat; + return this.format.getDate(); } + @Deprecated public void setDateFormat(String dateFormat) { - this.dateFormat = dateFormat; + this.format.setDate(dateFormat); } - public String getTimeFormat() { - return this.timeFormat; - } - - public void setTimeFormat(final String timeFormat) { - this.timeFormat = timeFormat; - } - - public String getDateTimeFormat() { - return this.dateTimeFormat; - } - - public void setDateTimeFormat(final String dateTimeFormat) { - this.dateTimeFormat = dateTimeFormat; + public Format getFormat() { + return this.format; } public boolean isIgnoreDefaultModelOnRedirect() { @@ -473,6 +451,49 @@ public class WebMvcProperties { } + public static class Format { + + /** + * Date format to use, for example `dd/MM/yyyy`. + */ + private String date; + + /** + * Time format to use, for example `HH:mm:ss`. + */ + private String time; + + /** + * Date-time format to use, for example `yyyy-MM-dd HH:mm:ss`. + */ + private String dateTime; + + public String getDate() { + return this.date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getTime() { + return this.time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getDateTime() { + return this.dateTime; + } + + public void setDateTime(String dateTime) { + this.dateTime = dateTime; + } + + } + public enum LocaleResolver { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java index e72f575b93..8f5b81e53b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/format/WebConversionServiceTests.java @@ -16,8 +16,13 @@ package org.springframework.boot.autoconfigure.web.format; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.Date; import org.junit.jupiter.api.Test; @@ -32,6 +37,14 @@ import static org.assertj.core.api.Assertions.assertThat; */ class WebConversionServiceTests { + @Test + void defaultDateFormat() { + WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()); + LocalDate date = LocalDate.of(2020, 4, 26); + assertThat(conversionService.convert(date, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(date)); + } + @Test void customDateFormatWithJavaUtilDate() { customDateFormat(Date.from(ZonedDateTime.of(2018, 1, 1, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant())); @@ -43,35 +56,49 @@ class WebConversionServiceTests { } @Test - void customTimeFormatWithJavaTime() { - customTimeFormat(java.time.LocalTime.of(13, 37, 42)); + void defaultTimeFormat() { + WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()); + LocalTime time = LocalTime.of(12, 45, 23); + assertThat(conversionService.convert(time, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(time)); } @Test - void customDateTimeFormatWithJavaTime() { - customDateTimeFormat(java.time.LocalDateTime.of(2019, 10, 28, 13, 37, 42)); + void customTimeFormat() { + WebConversionService conversionService = new WebConversionService( + new DateTimeFormatters().timeFormat("HH*mm*ss")); + LocalTime time = LocalTime.of(12, 45, 23); + assertThat(conversionService.convert(time, String.class)).isEqualTo("12*45*23"); } - private void customDateFormat(Object input) { - WebConversionService conversionService = new WebConversionService("dd*MM*yyyy"); - assertThat(conversionService.convert(input, String.class)).isEqualTo("01*01*2018"); + @Test + void defaultDateTimeFormat() { + WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 26, 12, 45, 23); + assertThat(conversionService.convert(dateTime, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(dateTime)); } - private void customTimeFormat(Object input) { - WebConversionService conversionService = new WebConversionService(null, "HH*mm*ss", null); - assertThat(conversionService.convert(input, String.class)).isEqualTo("13*37*42"); - } - - private void customDateTimeFormat(Object input) { - WebConversionService conversionService = new WebConversionService(null, null, "dd*MM*yyyy HH*mm*ss"); - assertThat(conversionService.convert(input, String.class)).isEqualTo("28*10*2019 13*37*42"); + @Test + void customDateTimeFormat() { + WebConversionService conversionService = new WebConversionService( + new DateTimeFormatters().dateTimeFormat("dd*MM*yyyy HH*mm*ss")); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 26, 12, 45, 23); + assertThat(conversionService.convert(dateTime, String.class)).isEqualTo("26*04*2020 12*45*23"); } @Test void convertFromStringToDate() { - WebConversionService conversionService = new WebConversionService("yyyy-MM-dd"); + WebConversionService conversionService = new WebConversionService( + new DateTimeFormatters().dateFormat("yyyy-MM-dd")); java.time.LocalDate date = conversionService.convert("2018-01-01", java.time.LocalDate.class); assertThat(date).isEqualTo(java.time.LocalDate.of(2018, 1, 1)); } + private void customDateFormat(Object input) { + WebConversionService conversionService = new WebConversionService( + new DateTimeFormatters().dateFormat("dd*MM*yyyy")); + assertThat(conversionService.convert(input, String.class)).isEqualTo("01*01*2018"); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 463fb25de0..af0efa7ea5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -16,8 +16,12 @@ package org.springframework.boot.autoconfigure.web.reactive; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.Collections; import java.util.Date; import java.util.List; @@ -185,7 +189,7 @@ class WebFluxAutoConfigurationTests { } @Test - void noDateFormat() { + void defaultDateFormat() { this.contextRunner.run((context) -> { FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); @@ -195,7 +199,7 @@ class WebFluxAutoConfigurationTests { } @Test - void overrideDateFormat() { + void customDateFormatWithDeprecatedProperty() { this.contextRunner.withPropertyValues("spring.webflux.date-format:dd*MM*yyyy").run((context) -> { FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); @@ -203,6 +207,53 @@ class WebFluxAutoConfigurationTests { }); } + @Test + void customDateFormat() { + this.contextRunner.withPropertyValues("spring.webflux.format.date:dd*MM*yyyy").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); + assertThat(conversionService.convert(date, String.class)).isEqualTo("25*06*1988"); + }); + } + + @Test + void defaultTimeFormat() { + this.contextRunner.run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalTime time = LocalTime.of(11, 43, 10); + assertThat(conversionService.convert(time, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(time)); + }); + } + + @Test + void customTimeFormat() { + this.contextRunner.withPropertyValues("spring.webflux.format.time=HH:mm:ss").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalTime time = LocalTime.of(11, 43, 10); + assertThat(conversionService.convert(time, String.class)).isEqualTo("11:43:10"); + }); + } + + @Test + void defaultDateTimeFormat() { + this.contextRunner.run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 28, 11, 43, 10); + assertThat(conversionService.convert(dateTime, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(dateTime)); + }); + } + + @Test + void customDateTimeTimeFormat() { + this.contextRunner.withPropertyValues("spring.webflux.format.date-time=yyyy-MM-dd HH:mm:ss").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 28, 11, 43, 10); + assertThat(conversionService.convert(dateTime, String.class)).isEqualTo("2020-04-28 11:43:10"); + }); + } + @Test void validatorWhenNoValidatorShouldUseDefault() { this.contextRunner.run((context) -> { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 4d4f5140b2..2877ae0251 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -16,8 +16,12 @@ package org.springframework.boot.autoconfigure.web.servlet; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; @@ -322,7 +326,7 @@ class WebMvcAutoConfigurationTests { } @Test - void noDateFormat() { + void defaultDateFormat() { this.contextRunner.run((context) -> { FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); @@ -332,7 +336,16 @@ class WebMvcAutoConfigurationTests { } @Test - void overrideDateFormat() { + void customDateFormat() { + this.contextRunner.withPropertyValues("spring.mvc.format.date:dd*MM*yyyy").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); + assertThat(conversionService.convert(date, String.class)).isEqualTo("25*06*1988"); + }); + } + + @Test + void customDateFormatWithDeprecatedProperty() { this.contextRunner.withPropertyValues("spring.mvc.date-format:dd*MM*yyyy").run((context) -> { FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); Date date = Date.from(ZonedDateTime.of(1988, 6, 25, 20, 30, 0, 0, ZoneId.systemDefault()).toInstant()); @@ -340,6 +353,44 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void defaultTimeFormat() { + this.contextRunner.run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalTime time = LocalTime.of(11, 43, 10); + assertThat(conversionService.convert(time, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(time)); + }); + } + + @Test + void customTimeFormat() { + this.contextRunner.withPropertyValues("spring.mvc.format.time=HH:mm:ss").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalTime time = LocalTime.of(11, 43, 10); + assertThat(conversionService.convert(time, String.class)).isEqualTo("11:43:10"); + }); + } + + @Test + void defaultDateTimeFormat() { + this.contextRunner.run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 28, 11, 43, 10); + assertThat(conversionService.convert(dateTime, String.class)) + .isEqualTo(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(dateTime)); + }); + } + + @Test + void customDateTimeTimeFormat() { + this.contextRunner.withPropertyValues("spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss").run((context) -> { + FormattingConversionService conversionService = context.getBean(FormattingConversionService.class); + LocalDateTime dateTime = LocalDateTime.of(2020, 4, 28, 11, 43, 10); + assertThat(conversionService.convert(dateTime, String.class)).isEqualTo("2020-04-28 11:43:10"); + }); + } + @Test void noMessageCodesResolver() { this.contextRunner.run(