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 a371b4cf906..365800aba88 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-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -19,11 +19,18 @@ package org.springframework.format.datetime; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.Locale; +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.util.Assert; +import org.springframework.util.StringUtils; /** * A formatter for {@link java.util.Date} types. @@ -31,15 +38,30 @@ import org.springframework.format.Formatter; * * @author Keith Donald * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 - * @see SimpleDateFormat + * @see SimpleDateFormat */ public class DateFormatter implements Formatter { + private static final Map ISO_PATTERNS; + static { + Map formats = new HashMap(); + formats.put(ISO.DATE, "yyyy-MM-dd"); + formats.put(ISO.TIME, "HH:mm:ss.SSSZ"); + formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + ISO_PATTERNS = Collections.unmodifiableMap(formats); + } + + private String pattern; private int style = DateFormat.DEFAULT; + private String stylePattern; + + private ISO iso; + private TimeZone timeZone; private boolean lenient = false; @@ -80,6 +102,32 @@ public class DateFormatter implements Formatter { this.style = style; } + /** + * 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 + *
    + *
  • 'S' = Small
  • + *
  • 'M' = Medium
  • + *
  • 'L' = Long
  • + *
  • 'F' = Full
  • + *
  • '-' = Omitted
  • + *
      + * This method mimics the styles supported by Joda Time. + * @param stylePattern two characters from the set {"S", "M", "L", "F", "-"} + * @since 3.2 + */ + public void setStylePattern(String stylePattern) { + this.stylePattern = stylePattern; + } + + /** + * Set the ISO format used for this date. + * @param iso the {@link ISO} format + * @since 3.2 + */ + public void setIso(ISO iso) { + this.iso = iso; + } /** * Set the TimeZone to normalize the date values into, if any. */ @@ -107,13 +155,7 @@ public class DateFormatter implements Formatter { protected DateFormat getDateFormat(Locale locale) { - DateFormat dateFormat; - if (this.pattern != null) { - dateFormat = new SimpleDateFormat(this.pattern, locale); - } - else { - dateFormat = DateFormat.getDateInstance(this.style, locale); - } + DateFormat dateFormat = createDateFormat(locale); if (this.timeZone != null) { dateFormat.setTimeZone(this.timeZone); } @@ -121,4 +163,45 @@ public class DateFormatter implements Formatter { return dateFormat; } + private DateFormat createDateFormat(Locale locale) { + if (StringUtils.hasLength(this.pattern)) { + return new SimpleDateFormat(this.pattern, locale); + } + if (iso != null && iso != ISO.NONE) { + String pattern = ISO_PATTERNS.get(iso); + Assert.state(pattern != null, "Unsupported ISO format " + iso); + SimpleDateFormat format = new SimpleDateFormat(pattern); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + if(StringUtils.hasLength(stylePattern)) { + int dateStyle = getStylePatternForChar(0); + int timeStyle = getStylePatternForChar(1); + if(dateStyle != -1 && timeStyle != -1) { + return DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale); + } + if(dateStyle != -1) { + return DateFormat.getDateInstance(dateStyle, locale); + } + if(timeStyle != -1) { + return DateFormat.getTimeInstance(timeStyle, locale); + } + throw new IllegalStateException("Unsupported style pattern '"+ stylePattern+ "'"); + + } + return DateFormat.getDateInstance(this.style, locale); + } + + private int getStylePatternForChar(int index) { + if(stylePattern != null && stylePattern.length() > index) { + switch (stylePattern.charAt(index)) { + case 'S': return DateFormat.SHORT; + case 'M': return DateFormat.MEDIUM; + case 'L': return DateFormat.LONG; + case 'F': return DateFormat.FULL; + case '-': return -1; + } + } + throw new IllegalStateException("Unsupported style pattern '"+ stylePattern+ "'"); + } } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java new file mode 100644 index 00000000000..21c267c111a --- /dev/null +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2012 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; + +import java.util.Calendar; +import java.util.Date; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.format.FormatterRegistrar; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar; +import org.springframework.util.Assert; + +/** + * Configures Date formatting for use with Spring. + *

      + * Designed for direct instantiation but also exposes the static + * {@link #addDateConverters(ConverterRegistry)} utility method for ad hoc use against any + * {@code ConverterRegistry} instance. + * + * @author Phillip Webb + * @since 3.2 + * @see JodaTimeFormatterRegistrar + * @see FormatterRegistrar#registerFormatters + */ +public class DateFormatterRegistrar implements FormatterRegistrar { + + + private DateFormatter dateFormatter = new DateFormatter(); + + + public void registerFormatters(FormatterRegistry registry) { + addDateConverters(registry); + registry.addFormatter(dateFormatter); + registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory()); + } + + /** + * Set the date formatter to register. If not specified default {@link DateFormatter} + * will be used. This method can be used if additional formatter configuration is + * required. + * @param dateFormatter the date formatter + */ + public void setFormatter(DateFormatter dateFormatter) { + Assert.notNull(dateFormatter,"DateFormatter must not be null"); + this.dateFormatter = dateFormatter; + } + + /** + * Add date converters to the specified registry. + * @param converterRegistry the registry of converters to add to + */ + public static void addDateConverters(ConverterRegistry converterRegistry) { + converterRegistry.addConverter(new DateToLongConverter()); + converterRegistry.addConverter(new DateToCalendarConverter()); + converterRegistry.addConverter(new CalendarToDateConverter()); + converterRegistry.addConverter(new CalendarToLongConverter()); + converterRegistry.addConverter(new LongToDateConverter()); + converterRegistry.addConverter(new LongToCalendarConverter()); + } + + + private static class DateToLongConverter implements Converter { + public Long convert(Date source) { + return source.getTime(); + } + } + + + private static class DateToCalendarConverter implements Converter { + public Calendar convert(Date source) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(source); + return calendar; + } + } + + + private static class CalendarToDateConverter implements Converter { + public Date convert(Calendar source) { + return source.getTime(); + } + } + + + private static class CalendarToLongConverter implements Converter { + public Long convert(Calendar source) { + return source.getTime().getTime(); + } + } + + + private static class LongToDateConverter implements Converter { + public Date convert(Long source) { + return new Date(source); + } + } + + + private static class LongToCalendarConverter implements Converter { + + private DateToCalendarConverter dateToCalendarConverter = new DateToCalendarConverter(); + + public Calendar convert(Long source) { + return dateToCalendarConverter.convert(new Date(source)); + } + } +} 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 new file mode 100644 index 00000000000..6e6da41a4c4 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateTimeFormatAnnotationFormatterFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2012 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; + +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.format.AnnotationFormatterFactory; +import org.springframework.format.Formatter; +import org.springframework.format.Parser; +import org.springframework.format.Printer; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory; +import org.springframework.util.StringValueResolver; + +/** + * Formats fields annotated with the {@link DateTimeFormat} annotation. + * + * @author Phillip Webb + * @see JodaDateTimeFormatAnnotationFormatterFactory + * @since 3.2 + */ +public class DateTimeFormatAnnotationFormatterFactory implements + AnnotationFormatterFactory, EmbeddedValueResolverAware { + + + private static final Set> FIELD_TYPES; + static { + Set> fieldTypes = new HashSet>(); + fieldTypes.add(Date.class); + fieldTypes.add(Calendar.class); + fieldTypes.add(Long.class); + FIELD_TYPES = Collections.unmodifiableSet(fieldTypes); + } + + + private StringValueResolver embeddedValueResolver; + + + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + + public Set> getFieldTypes() { + return FIELD_TYPES; + } + + public Printer getPrinter(DateTimeFormat annotation, Class fieldType) { + return getFormatter(annotation, fieldType); + } + + public Parser getParser(DateTimeFormat annotation, Class fieldType) { + return getFormatter(annotation, fieldType); + } + + protected Formatter getFormatter(DateTimeFormat annotation, Class fieldType) { + DateFormatter formatter = new DateFormatter(); + formatter.setStylePattern(resolveEmbeddedValue(annotation.style())); + formatter.setIso(annotation.iso()); + formatter.setPattern(resolveEmbeddedValue(annotation.pattern())); + return formatter; + } + + protected String resolveEmbeddedValue(String value) { + return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); + } +} diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaDateTimeFormatAnnotationFormatterFactory.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaDateTimeFormatAnnotationFormatterFactory.java index a95745884d9..e2b49623a54 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaDateTimeFormatAnnotationFormatterFactory.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaDateTimeFormatAnnotationFormatterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -39,7 +39,7 @@ import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; /** - * Formats fields annotated with the {@link DateTimeFormat} annotation. + * Formats fields annotated with the {@link DateTimeFormat} annotation using Joda time. * * @author Keith Donald * @author Juergen Hoeller 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 e7d7118fb65..6943ad4241f 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 @@ -29,6 +29,7 @@ import org.joda.time.MutableDateTime; import org.joda.time.ReadableInstant; 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 Joda Time support into Spring's field formatting system. @@ -43,6 +44,7 @@ final class JodaTimeConverters { * @param registry the converter registry */ public static void registerConverters(ConverterRegistry registry) { + DateFormatterRegistrar.addDateConverters(registry); registry.addConverter(new DateTimeToLocalDateConverter()); registry.addConverter(new DateTimeToLocalTimeConverter()); registry.addConverter(new DateTimeToLocalDateTimeConverter()); @@ -52,7 +54,6 @@ final class JodaTimeConverters { registry.addConverter(new DateTimeToDateConverter()); registry.addConverter(new DateTimeToCalendarConverter()); registry.addConverter(new DateTimeToLongConverter()); - registry.addConverter(new DateToLongConverter()); registry.addConverter(new CalendarToReadableInstantConverter()); } @@ -147,18 +148,7 @@ final class JodaTimeConverters { } } - /** - * Used when printing a java.util.Date field with a MillisecondInstantPrinter. - * @see MillisecondInstantPrinter - * @see JodaDateTimeFormatAnnotationFormatterFactory - */ - private static class DateToLongConverter implements Converter { - public Long convert(Date source) { - return source.getTime(); - } - } - - /** + /** * Used when printing a java.util.Calendar field with a ReadableInstantPrinter. * @see MillisecondInstantPrinter * @see JodaDateTimeFormatAnnotationFormatterFactory diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java index b60a6b3a0a6..3a8a34222cc 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeFormatterRegistrar.java @@ -42,6 +42,7 @@ import org.springframework.format.Printer; * @see #setDateTimeStyle * @see #setUseIsoFormat * @see FormatterRegistrar#registerFormatters + * @see DateFormatterRegistrar */ public class JodaTimeFormatterRegistrar implements FormatterRegistrar { diff --git a/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java b/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java index 47a410b7fbe..5ed55dc0eb2 100644 --- a/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java +++ b/spring-context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,18 +16,9 @@ package org.springframework.format.support; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; - import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.format.AnnotationFormatterFactory; import org.springframework.format.FormatterRegistry; -import org.springframework.format.Parser; -import org.springframework.format.Printer; -import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.datetime.DateFormatterRegistrar; import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar; import org.springframework.format.number.NumberFormatAnnotationFormatterFactory; import org.springframework.util.ClassUtils; @@ -96,39 +87,9 @@ public class DefaultFormattingConversionService extends FormattingConversionServ formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); if (jodaTimePresent) { new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry); - } else { - formatterRegistry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory()); - } - } - - - /** - * Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used - * without the JodaTime library being present. - */ - private static final class NoJodaDateTimeFormatAnnotationFormatterFactory - implements AnnotationFormatterFactory { - - private final Set> fieldTypes; - - public NoJodaDateTimeFormatAnnotationFormatterFactory() { - Set> rawFieldTypes = new HashSet>(4); - rawFieldTypes.add(Date.class); - rawFieldTypes.add(Calendar.class); - rawFieldTypes.add(Long.class); - this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes); - } - - public Set> getFieldTypes() { - return this.fieldTypes; } - - public Printer getPrinter(DateTimeFormat annotation, Class fieldType) { - throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported"); - } - - public Parser getParser(DateTimeFormat annotation, Class fieldType) { - throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported"); + else { + new DateFormatterRegistrar().registerFormatters(formatterRegistry); } } diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormatterTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormatterTests.java index 668f420aab1..3df68cdd88d 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormatterTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormatterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 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,39 +16,217 @@ package org.springframework.format.datetime; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.text.DateFormat; import java.text.ParseException; import java.util.Calendar; +import java.util.Date; import java.util.Locale; +import java.util.TimeZone; -import static org.junit.Assert.*; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.junit.Rule; import org.junit.Test; -import org.springframework.format.datetime.DateFormatter; +import org.junit.rules.ExpectedException; +import org.springframework.format.annotation.DateTimeFormat.ISO; /** + * Tests for {@link DateFormatter}. + * * @author Keith Donald + * @author Phillip Webb */ public class DateFormatterTests { - private DateFormatter formatter = new DateFormatter("yyyy-MM-dd"); - + @Rule + public ExpectedException thown = ExpectedException.none(); + + private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + @Test - public void formatValue() { - Calendar cal = Calendar.getInstance(Locale.US); - cal.clear(); - cal.set(Calendar.YEAR, 2009); - cal.set(Calendar.MONTH, Calendar.JUNE); - cal.set(Calendar.DAY_OF_MONTH, 1); - assertEquals("2009-06-01", formatter.print(cal.getTime(), Locale.US)); + public void shouldPrintAndParseDefault() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("Jun 1, 2009")); + assertThat(formatter.parse("Jun 1, 2009", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseFromPattern() throws ParseException { + DateFormatter formatter = new DateFormatter("yyyy-MM-dd"); + formatter.setTimeZone(UTC); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("2009-06-01")); + assertThat(formatter.parse("2009-06-01", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseShort() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStyle(DateFormat.SHORT); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("6/1/09")); + assertThat(formatter.parse("6/1/09", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseMedium() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStyle(DateFormat.MEDIUM); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("Jun 1, 2009")); + assertThat(formatter.parse("Jun 1, 2009", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseLong() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStyle(DateFormat.LONG); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("June 1, 2009")); + assertThat(formatter.parse("June 1, 2009", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseFull() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStyle(DateFormat.FULL); + Date date = getDate(2009, Calendar.JUNE, 1); + assertThat(formatter.print(date, Locale.US), is("Monday, June 1, 2009")); + assertThat(formatter.parse("Monday, June 1, 2009", Locale.US), is(date)); + } + + @Test + public void shouldPrintAndParseISODate() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setIso(ISO.DATE); + Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3); + assertThat(formatter.print(date, Locale.US), is("2009-06-01")); + assertThat(formatter.parse("2009-6-01", Locale.US), + is(getDate(2009, Calendar.JUNE, 1))); + } + + @Test + public void shouldPrintAndParseISOTime() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setIso(ISO.TIME); + Date date = getDate(2009, Calendar.JANUARY, 1, 14, 23, 5, 3); + assertThat(formatter.print(date, Locale.US), is("14:23:05.003+0000")); + assertThat(formatter.parse("14:23:05.003+0000", Locale.US), + is(getDate(1970, Calendar.JANUARY, 1, 14, 23, 5, 3))); } - + @Test - public void parseValue() throws ParseException { + public void shouldParseIsoTimeWithZeros() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setIso(ISO.TIME); + Date date = formatter.parse("12:00:00.000-00005", Locale.US); + System.out.println(date); + } + + @Test + public void shouldPrintAndParseISODateTime() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setIso(ISO.DATE_TIME); + Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3); + assertThat(formatter.print(date, Locale.US), is("2009-06-01T14:23:05.003+0000")); + assertThat(formatter.parse("2009-06-01T14:23:05.003+0000", Locale.US), is(date)); + } + + @Test + public void shouldSupportJodaStylePatterns() throws Exception { + String[] chars = { "S", "M", "L", "F", "-" }; + for (String d : chars) { + for (String t : chars) { + String style = d + t; + if (!style.equals("--")) { + Date date = getDate(2009, Calendar.JUNE, 10, 14, 23, 0, 0); + if (t.equals("-")) { + date = getDate(2009, Calendar.JUNE, 10); + } else if (d.equals("-")) { + date = getDate(1970, Calendar.JANUARY, 1, 14, 23, 0, 0); + } + testJodaStylePatterns(style, Locale.US, date); + } + } + } + } + + private void testJodaStylePatterns(String style, Locale locale, Date date) + throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStylePattern(style); + DateTimeFormatter jodaFormatter = DateTimeFormat.forStyle(style).withLocale( + locale).withZone(DateTimeZone.UTC); + String jodaPrinted = jodaFormatter.print(date.getTime()); + assertThat("Unable to print style pattern " + style, + formatter.print(date, locale), is(equalTo(jodaPrinted))); + assertThat("Unable to parse style pattern " + style, + formatter.parse(jodaPrinted, locale), is(equalTo(date))); + } + + @Test + public void shouldThrowOnUnsupportStylePattern() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setStylePattern("OO"); + thown.expect(IllegalStateException.class); + thown.expectMessage("Unsupported style pattern 'OO'"); + formatter.parse("2009", Locale.US); + } + + @Test + public void shouldUseCorrectOrder() throws Exception { + DateFormatter formatter = new DateFormatter(); + formatter.setTimeZone(UTC); + formatter.setStyle(DateFormat.SHORT); + formatter.setStylePattern("L-"); + formatter.setIso(ISO.DATE_TIME); + formatter.setPattern("yyyy"); + Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3); + + assertThat("uses pattern",formatter.print(date, Locale.US), is("2009")); + + formatter.setPattern(""); + assertThat("uses ISO", formatter.print(date, Locale.US), is("2009-06-01T14:23:05.003+0000")); + + formatter.setIso(ISO.NONE); + assertThat("uses style pattern", formatter.print(date, Locale.US), is("June 1, 2009")); + + formatter.setStylePattern(""); + assertThat("uses style", formatter.print(date, Locale.US), is("6/1/09")); + } + + private Date getDate(int year, int month, int dayOfMonth) { + return getDate(year, month, dayOfMonth, 0, 0, 0, 0); + } + + private Date getDate(int year, int month, int dayOfMonth, int hour, int minute, + int second, int millisecond) { Calendar cal = Calendar.getInstance(Locale.US); + cal.setTimeZone(UTC); cal.clear(); - cal.set(Calendar.YEAR, 2009); - cal.set(Calendar.MONTH, Calendar.JUNE); - cal.set(Calendar.DAY_OF_MONTH, 1); - assertEquals(cal.getTime(), formatter.parse("2009-06-01", Locale.US)); + cal.set(Calendar.YEAR, year); + cal.set(Calendar.MONTH, month); + cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); + cal.set(Calendar.HOUR, hour); + cal.set(Calendar.MINUTE, minute); + cal.set(Calendar.SECOND, second); + cal.set(Calendar.MILLISECOND, millisecond); + return cal.getTime(); } } 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 new file mode 100644 index 00000000000..087e8e95214 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java @@ -0,0 +1,300 @@ +/* + * Copyright 2002-2012 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; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; +import org.springframework.format.datetime.DateFormatterRegistrar; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.validation.DataBinder; + +/** + * @author Phillip Webb + * @author Keith Donald + * @author Juergen Hoeller + */ +public class DateFormattingTests { + + private FormattingConversionService conversionService = new FormattingConversionService(); + + private DataBinder binder; + + @Before + public void setUp() { + DefaultConversionService.addDefaultConverters(conversionService); + + DateFormatterRegistrar registrar = new DateFormatterRegistrar(); + registrar.registerFormatters(conversionService); + + SimpleDateBean bean = new SimpleDateBean(); + bean.getChildren().add(new SimpleDateBean()); + binder = new DataBinder(bean); + binder.setConversionService(conversionService); + + LocaleContextHolder.setLocale(Locale.US); + } + + @After + public void tearDown() { + LocaleContextHolder.setLocale(null); + } + + @Test + public void testBindDate() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("date", "10/31/09 12:00 PM"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("date")); + } + + @Test + public void testBindDateArray() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("date", new String[] {"10/31/09 12:00 PM"}); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + } + + @Test + public void testBindDateAnnotated() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("dateAnnotated", "10/31/09"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09", binder.getBindingResult().getFieldValue("dateAnnotated")); + } + + @Test + public void testBindDateAnnotatedWithError() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("dateAnnotated", "Oct X31, 2009"); + binder.bind(propertyValues); + assertEquals(1, binder.getBindingResult().getFieldErrorCount("dateAnnotated")); + assertEquals("Oct X31, 2009", binder.getBindingResult().getFieldValue("dateAnnotated")); + } + + @Test + @Ignore + public 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"); + binder.bind(propertyValues); + assertEquals(1, binder.getBindingResult().getFieldErrorCount("dateAnnotated")); + assertEquals("Oct 031, 2009", binder.getBindingResult().getFieldValue("dateAnnotated")); + } + + @Test + public void testBindCalendar() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("calendar", "10/31/09 12:00 PM"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("calendar")); + } + + @Test + public void testBindCalendarAnnotated() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("calendarAnnotated", "10/31/09"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09", binder.getBindingResult().getFieldValue("calendarAnnotated")); + } + + @Test + public void testBindLong() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("millis", "1256961600"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("1256961600", binder.getBindingResult().getFieldValue("millis")); + } + + @Test + public void testBindLongAnnotated() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("millisAnnotated", "10/31/09"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09", binder.getBindingResult().getFieldValue("millisAnnotated")); + } + + @Test + public void testBindISODate() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("isoDate", "2009-10-31"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("2009-10-31", binder.getBindingResult().getFieldValue("isoDate")); + } + + @Test + public void testBindISOTime() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("isoTime", "12:00:00.000-0500"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("17:00:00.000+0000", binder.getBindingResult().getFieldValue("isoTime")); + } + + @Test + public void testBindISODateTime() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("isoDateTime", "2009-10-31T12:00:00.000-0800"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("2009-10-31T20:00:00.000+0000", binder.getBindingResult().getFieldValue("isoDateTime")); + } + + @Test + public void testBindNestedDateAnnotated() { + MutablePropertyValues propertyValues = new MutablePropertyValues(); + propertyValues.add("children[0].dateAnnotated", "10/31/09"); + binder.bind(propertyValues); + assertEquals(0, binder.getBindingResult().getErrorCount()); + assertEquals("10/31/09", binder.getBindingResult().getFieldValue("children[0].dateAnnotated")); + } + + @SuppressWarnings("unused") + private static class SimpleDateBean { + + @DateTimeFormat + private Date date; + + @DateTimeFormat(style="S-") + private Date dateAnnotated; + + @DateTimeFormat + private Calendar calendar; + + @DateTimeFormat(style="S-") + private Calendar calendarAnnotated; + + private Long millis; + + private Long millisAnnotated; + + @DateTimeFormat(pattern="M/d/yy h:mm a") + private Date dateAnnotatedPattern; + + @DateTimeFormat(iso=ISO.DATE) + private Date isoDate; + + @DateTimeFormat(iso=ISO.TIME) + private Date isoTime; + + @DateTimeFormat(iso=ISO.DATE_TIME) + private Date isoDateTime; + + private final List children = new ArrayList(); + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Date getDateAnnotated() { + return dateAnnotated; + } + + public void setDateAnnotated(Date dateAnnotated) { + this.dateAnnotated = dateAnnotated; + } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(Calendar calendar) { + this.calendar = calendar; + } + + public Calendar getCalendarAnnotated() { + return calendarAnnotated; + } + + public void setCalendarAnnotated(Calendar calendarAnnotated) { + this.calendarAnnotated = calendarAnnotated; + } + + public Long getMillis() { + return millis; + } + + public void setMillis(Long millis) { + this.millis = millis; + } + + @DateTimeFormat(style="S-") + public Long getMillisAnnotated() { + return millisAnnotated; + } + + public void setMillisAnnotated(@DateTimeFormat(style="S-") Long millisAnnotated) { + this.millisAnnotated = millisAnnotated; + } + + public Date getIsoDate() { + return isoDate; + } + + public void setIsoDate(Date isoDate) { + this.isoDate = isoDate; + } + + public Date getIsoTime() { + return isoTime; + } + + public void setIsoTime(Date isoTime) { + this.isoTime = isoTime; + } + + public Date getIsoDateTime() { + return isoDateTime; + } + + public void setIsoDateTime(Date isoDateTime) { + this.isoDateTime = isoDateTime; + } + + public List getChildren() { + return children; + } + } + +}