Support DateTimeFormat annotation without Joda

Dependency on Joda Time when using the @DateTimeFormat annotation is
now optional. If Joda Time is not present the JDK SimpleDateFormat
will be used to parse and print date patterns. If Joda time is
present it will always be used in preference to SimpleDateFormat.

Issue: SPR-6508
This commit is contained in:
Phillip Webb
2012-09-30 10:00:20 -07:00
parent ddddec8719
commit a26059f94a
9 changed files with 806 additions and 85 deletions

View File

@@ -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 parseValue() throws ParseException {
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 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();
}
}

View File

@@ -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<SimpleDateBean> children = new ArrayList<SimpleDateBean>();
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<SimpleDateBean> getChildren() {
return children;
}
}
}