diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java index 02ba64b613..b5a3b09dc4 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java @@ -36,8 +36,13 @@ import org.springframework.format.datetime.DateFormatterRegistrar; * Installs lower-level type converters required to integrate * Joda-Time support into Spring's field formatting system. * + *

Note: {@link JodaTimeFormatterRegistrar} installs these converters + * and relies on several of them for its formatters. Some additional + * converters are just being registered for custom conversion scenarios. + * * @author Keith Donald * @author Phillip Webb + * @author Juergen Hoeller * @since 3.0 */ final class JodaTimeConverters { @@ -48,6 +53,7 @@ final class JodaTimeConverters { */ public static void registerConverters(ConverterRegistry registry) { DateFormatterRegistrar.addDateConverters(registry); + registry.addConverter(new DateTimeToLocalDateConverter()); registry.addConverter(new DateTimeToLocalTimeConverter()); registry.addConverter(new DateTimeToLocalDateTimeConverter()); @@ -59,6 +65,11 @@ final class JodaTimeConverters { registry.addConverter(new DateTimeToLongConverter()); registry.addConverter(new DateToReadableInstantConverter()); registry.addConverter(new CalendarToReadableInstantConverter()); + registry.addConverter(new LongToReadableInstantConverter()); + registry.addConverter(new LocalDateTimeToLocalDateConverter()); + registry.addConverter(new LocalDateTimeToLocalTimeConverter()); + registry.addConverter(new LocalDateToDateMidnightConverter()); + registry.addConverter(new DateMidnightToLocalDateConverter()); } @@ -144,7 +155,7 @@ final class JodaTimeConverters { /** - * Used when printing a java.util.Date field with a ReadableInstantPrinter. + * Used when printing a {@code java.util.Date} field with a ReadableInstantPrinter. * @see MillisecondInstantPrinter * @see JodaDateTimeFormatAnnotationFormatterFactory */ @@ -158,7 +169,7 @@ final class JodaTimeConverters { /** - * Used when printing a java.util.Calendar field with a ReadableInstantPrinter. + * Used when printing a {@code java.util.Calendar} field with a ReadableInstantPrinter. * @see MillisecondInstantPrinter * @see JodaDateTimeFormatAnnotationFormatterFactory */ @@ -170,4 +181,54 @@ final class JodaTimeConverters { } } + + /** + * Used when printing a Long field with a ReadableInstantPrinter. + * @see MillisecondInstantPrinter + * @see JodaDateTimeFormatAnnotationFormatterFactory + */ + private static class LongToReadableInstantConverter implements Converter { + + @Override + public ReadableInstant convert(Long source) { + return new DateTime(source.longValue()); + } + } + + + private static class LocalDateTimeToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(LocalDateTime source) { + return source.toLocalDate(); + } + } + + + private static class LocalDateTimeToLocalTimeConverter implements Converter { + + @Override + public LocalTime convert(LocalDateTime source) { + return source.toLocalTime(); + } + } + + + private static class LocalDateToDateMidnightConverter implements Converter { + + @Override + public DateMidnight convert(LocalDate source) { + return source.toDateMidnight(); + } + } + + + private static class DateMidnightToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(DateMidnight source) { + return source.toLocalDate(); + } + } + } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java new file mode 100644 index 0000000000..d1a4919268 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeConverters.java @@ -0,0 +1,267 @@ +/* + * Copyright 2002-2013 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 + * + * http://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.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoZonedDateTime; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.format.datetime.DateFormatterRegistrar; + +/** + * Installs lower-level type converters required to integrate + * JSR-310 support into Spring's field formatting system. + * + *

Note: {@link DateTimeFormatterRegistrar} installs these converters but + * does not rely on them for its formatters. They are just being registered + * for custom conversion scenarios between different JSR-310 value types + * and also between {@link java.util.Calendar} and JSR-310 value types. + * + * @author Juergen Hoeller + * @since 4.0.1 + */ +final class DateTimeConverters { + + /** + * Install the converters into the converter registry. + * @param registry the converter registry + */ + public static void registerConverters(ConverterRegistry registry) { + DateFormatterRegistrar.addDateConverters(registry); + + registry.addConverter(new LocalDateTimeToLocalDateConverter()); + registry.addConverter(new LocalDateTimeToLocalTimeConverter()); + registry.addConverter(new ZonedDateTimeToLocalDateConverter()); + registry.addConverter(new ZonedDateTimeToLocalTimeConverter()); + registry.addConverter(new ZonedDateTimeToLocalDateTimeConverter()); + registry.addConverter(new ZonedDateTimeToOffsetDateTimeConverter()); + registry.addConverter(new ZonedDateTimeToInstantConverter()); + registry.addConverter(new OffsetDateTimeToLocalDateConverter()); + registry.addConverter(new OffsetDateTimeToLocalTimeConverter()); + registry.addConverter(new OffsetDateTimeToLocalDateTimeConverter()); + registry.addConverter(new OffsetDateTimeToZonedDateTimeConverter()); + registry.addConverter(new OffsetDateTimeToInstantConverter()); + registry.addConverter(new CalendarToZonedDateTimeConverter()); + registry.addConverter(new CalendarToOffsetDateTimeConverter()); + registry.addConverter(new CalendarToLocalDateConverter()); + registry.addConverter(new CalendarToLocalTimeConverter()); + registry.addConverter(new CalendarToLocalDateTimeConverter()); + registry.addConverter(new CalendarToInstantConverter()); + registry.addConverter(new LongToInstantConverter()); + registry.addConverter(new InstantToLongConverter()); + } + + private static ZonedDateTime calendarToZonedDateTime(Calendar source) { + if (source instanceof GregorianCalendar) { + return ((GregorianCalendar) source).toZonedDateTime(); + } + else { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(source.getTimeInMillis()), + source.getTimeZone().toZoneId()); + } + } + + + private static class LocalDateTimeToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(LocalDateTime source) { + return source.toLocalDate(); + } + } + + + private static class LocalDateTimeToLocalTimeConverter implements Converter { + + @Override + public LocalTime convert(LocalDateTime source) { + return source.toLocalTime(); + } + } + + + private static class ZonedDateTimeToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(ZonedDateTime source) { + return source.toLocalDate(); + } + } + + + private static class ZonedDateTimeToLocalTimeConverter implements Converter { + + @Override + public LocalTime convert(ZonedDateTime source) { + return source.toLocalTime(); + } + } + + + private static class ZonedDateTimeToLocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(ZonedDateTime source) { + return source.toLocalDateTime(); + } + } + + + private static class ZonedDateTimeToOffsetDateTimeConverter implements Converter { + + @Override + public OffsetDateTime convert(ZonedDateTime source) { + return source.toOffsetDateTime(); + } + } + + + private static class ZonedDateTimeToInstantConverter implements Converter { + + @Override + public Instant convert(ZonedDateTime source) { + // Explicit cast to interface necessary in order to call Java 8 default method from -source 1.6 + return ((ChronoZonedDateTime) source).toInstant(); + } + } + + private static class OffsetDateTimeToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(OffsetDateTime source) { + return source.toLocalDate(); + } + } + + + private static class OffsetDateTimeToLocalTimeConverter implements Converter { + + @Override + public LocalTime convert(OffsetDateTime source) { + return source.toLocalTime(); + } + } + + + private static class OffsetDateTimeToLocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(OffsetDateTime source) { + return source.toLocalDateTime(); + } + } + + + private static class OffsetDateTimeToZonedDateTimeConverter implements Converter { + + @Override + public ZonedDateTime convert(OffsetDateTime source) { + return source.toZonedDateTime(); + } + } + + + private static class OffsetDateTimeToInstantConverter implements Converter { + + @Override + public Instant convert(OffsetDateTime source) { + return source.toInstant(); + } + } + + + private static class CalendarToZonedDateTimeConverter implements Converter { + + @Override + public ZonedDateTime convert(Calendar source) { + return calendarToZonedDateTime(source); + } + } + + + private static class CalendarToOffsetDateTimeConverter implements Converter { + + @Override + public OffsetDateTime convert(Calendar source) { + return calendarToZonedDateTime(source).toOffsetDateTime(); + } + } + + + private static class CalendarToLocalDateConverter implements Converter { + + @Override + public LocalDate convert(Calendar source) { + return calendarToZonedDateTime(source).toLocalDate(); + } + } + + + private static class CalendarToLocalTimeConverter implements Converter { + + @Override + public LocalTime convert(Calendar source) { + return calendarToZonedDateTime(source).toLocalTime(); + } + } + + + private static class CalendarToLocalDateTimeConverter implements Converter { + + @Override + public LocalDateTime convert(Calendar source) { + return calendarToZonedDateTime(source).toLocalDateTime(); + } + } + + + private static class CalendarToInstantConverter implements Converter { + + @Override + public Instant convert(Calendar source) { + // Explicit cast to interface necessary in order to call Java 8 default method from -source 1.6 + return ((ChronoZonedDateTime) calendarToZonedDateTime(source)).toInstant(); + } + } + + + private static class LongToInstantConverter implements Converter { + + @Override + public Instant convert(Long source) { + return Instant.ofEpochMilli(source); + } + } + + + private static class InstantToLongConverter implements Converter { + + @Override + public Long convert(Instant source) { + return source.toEpochMilli(); + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterRegistrar.java index c3ba262173..a514b65c28 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterRegistrar.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeFormatterRegistrar.java @@ -108,7 +108,7 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar { /** * Set the formatter that will be used for objects representing date values. - *

This formatter will be used for the {@link org.joda.time.LocalDate} type. + *

This formatter will be used for the {@link LocalDate} type. * When specified, the {@link #setDateStyle dateStyle} and * {@link #setUseIsoFormat useIsoFormat} properties will be ignored. * @param formatter the formatter to use @@ -121,8 +121,8 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar { /** * Set the formatter that will be used for objects representing time values. - *

This formatter will be used for the {@link org.joda.time.LocalTime} type. - * When specified, the {@link #setTimeStyle timeStyle} and + *

This formatter will be used for the {@link LocalTime} and {@link OffsetTime} + * types. When specified, the {@link #setTimeStyle timeStyle} and * {@link #setUseIsoFormat useIsoFormat} properties will be ignored. * @param formatter the formatter to use * @see #setDateFormatter @@ -134,9 +134,9 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar { /** * Set the formatter that will be used for objects representing date and time values. - *

This formatter will be used for {@link org.joda.time.LocalDateTime}, {@link org.joda.time.ReadableInstant}, - * {@link java.util.Date} and {@link java.util.Calendar} types. - * When specified, the {@link #setDateTimeStyle dateTimeStyle} and + *

This formatter will be used for {@link LocalDateTime}, {@link ZonedDateTime} + * and {@link OffsetDateTime} types. When specified, the + * {@link #setDateTimeStyle dateTimeStyle} and * {@link #setUseIsoFormat useIsoFormat} properties will be ignored. * @param formatter the formatter to use * @see #setDateFormatter @@ -149,6 +149,8 @@ public class DateTimeFormatterRegistrar implements FormatterRegistrar { @Override public void registerFormatters(FormatterRegistry registry) { + DateTimeConverters.registerConverters(registry); + DateTimeFormatter dateFormatter = getFormatter(Type.DATE); DateTimeFormatter timeFormatter = getFormatter(Type.TIME); DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME); 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 d12f311061..ceefd049f9 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 @@ -16,7 +16,7 @@ package org.springframework.format.datetime.standard; -import java.lang.reflect.Method; +import java.text.DateFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -25,8 +25,10 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.ArrayList; +import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; +import java.util.TimeZone; import org.junit.After; import org.junit.Before; @@ -34,7 +36,6 @@ import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; @@ -171,6 +172,15 @@ public class DateTimeFormattingTests { assertEquals("Oct -31, 2009", binder.getBindingResult().getFieldValue("localDateAnnotated")); } + @Test + public void testBindLocalDateFromJavaUtilCalendar() throws Exception { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("localDate", new GregorianCalendar(2009, 9, 31, 0, 0)); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09", binder.getBindingResult().getFieldValue("localDate")); + } + @Test public void testBindLocalTime() { MutablePropertyValues propertyValues = new MutablePropertyValues(); @@ -213,6 +223,15 @@ public class DateTimeFormattingTests { assertEquals("12:00:00 PM", binder.getBindingResult().getFieldValue("localTimeAnnotated")); } + @Test + public void testBindLocalTimeFromJavaUtilCalendar() throws Exception { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("localTime", new GregorianCalendar(1970, 0, 0, 12, 0)); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("12:00 PM", binder.getBindingResult().getFieldValue("localTime")); + } + @Test public void testBindLocalDateTime() { MutablePropertyValues propertyValues = new MutablePropertyValues(); @@ -231,6 +250,15 @@ public class DateTimeFormattingTests { assertEquals("Oct 31, 2009 12:00:00 PM", binder.getBindingResult().getFieldValue("localDateTimeAnnotated")); } + @Test + public void testBindLocalDateTimeFromJavaUtilCalendar() throws Exception { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("localDateTime", new GregorianCalendar(2009, 9, 31, 12, 0)); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("localDateTime")); + } + @Test public void testBindDateTimeWithSpecificStyle() throws Exception { DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); @@ -241,9 +269,6 @@ public class DateTimeFormattingTests { binder.bind(propertyValues); assertEquals(0, binder.getBindingResult().getErrorCount()); assertEquals("Oct 31, 2009 12:00:00 PM", binder.getBindingResult().getFieldValue("localDateTime")); - Method testMethod = LocalVariableTableParameterNameDiscoverer.class.getMethod("getParameterNames", Method.class); - System.out.println(testMethod.getParameters()[0].getName()); - System.out.println(new LocalVariableTableParameterNameDiscoverer().getParameterNames(testMethod)[0]); } @Test @@ -291,6 +316,17 @@ public class DateTimeFormattingTests { assertTrue(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31T12:00")); } + @Test + public void testBindInstantFromJavaUtilDate() throws Exception { + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("instant", df.parse("10/31/09 12:00 PM")); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertTrue(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31T12:00")); + } + public static class DateTimeBean {