Merge pull request #161 from philwebb/SPR-7121

* SPR-7121:
  Support for custom global Joda DateTimeFormatters
  Move JRuby dependency below Joda
  Support DateTimeFormat annotation without Joda
  Corrected date pattern in JavaDocs
  Polish whitespace and formatting
This commit is contained in:
Phillip Webb
2012-11-03 17:12:14 -07:00
14 changed files with 1364 additions and 249 deletions

View File

@@ -262,12 +262,12 @@ project('spring-context') {
}
compile("org.beanshell:bsh:2.0b4", optional)
compile("org.codehaus.groovy:groovy-all:1.6.3", optional)
compile("org.jruby:jruby:1.4.0", optional)
compile("org.hibernate:hibernate-validator:4.2.0.Final") { dep ->
optional dep
exclude group: 'org.slf4j', module: 'slf4j-api'
}
compile("joda-time:joda-time:1.6", optional)
compile("org.jruby:jruby:1.4.0", optional)
compile("javax.cache:cache-api:0.5", optional)
compile("net.sf.ehcache:ehcache-core:2.0.0", optional)
compile("org.slf4j:slf4j-api:1.6.1", optional)

View File

@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
* Supports formatting by style pattern, ISO date time pattern, or custom format pattern string.
* Can be applied to <code>java.util.Date</code>, <code>java.util.Calendar</code>, <code>java.long.Long</code>, or Joda Time fields.
* <p>
* For style-based formatting, set the {@link #style()} attribute to be the style pattern code.
* For style-based formatting, set the {@link #style()} attribute to be the style pattern code.
* The first character of the code is the date style, and the second character is the time style.
* Specify a character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' for full.
* A date or time may be omitted by specifying the style character '-'.
@@ -39,7 +39,7 @@ import java.lang.annotation.Target;
* When the pattern attribute is specified, it takes precedence over both the style and ISO attribute.
* When the iso attribute is specified, if takes precedence over the style attribute.
* When no annotation attributes are specified, the default format applied is style-based with a style code of 'SS' (short date, short time).
*
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
@@ -76,23 +76,23 @@ public @interface DateTimeFormat {
* Common ISO date time format patterns.
*/
public enum ISO {
/**
/**
* The most common ISO Date Format <code>yyyy-MM-dd</code> e.g. 2000-10-31.
*/
DATE,
/**
* The most common ISO Time Format <code>hh:mm:ss.SSSZ</code> e.g. 01:30:00.000-05:00.
/**
* The most common ISO Time Format <code>HH:mm:ss.SSSZ</code> e.g. 01:30:00.000-05:00.
*/
TIME,
/**
* The most common ISO DateTime Format <code>yyyy-MM-dd'T'hh:mm:ss.SSSZ</code> e.g. 2000-10-31 01:30:00.000-05:00.
/**
* The most common ISO DateTime Format <code>yyyy-MM-dd'T'HH:mm:ss.SSSZ</code> e.g. 2000-10-31 01:30:00.000-05:00.
* The default if no annotation value is specified.
*/
DATE_TIME,
/**
* Indicates that no ISO-based format pattern should be applied.
*/

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.
@@ -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<Date> {
private static final Map<ISO, String> ISO_PATTERNS;
static {
Map<ISO, String> formats = new HashMap<DateTimeFormat.ISO, String>();
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<Date> {
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
* <ul>
* <li>'S' = Small</li>
* <li>'M' = Medium</li>
* <li>'L' = Long</li>
* <li>'F' = Full</li>
* <li>'-' = Omitted</li>
* <ul>
* 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<Date> {
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<Date> {
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+ "'");
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.
* <p>
* 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.addFormatterForFieldType(Calendar.class, 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<Date, Long> {
public Long convert(Date source) {
return source.getTime();
}
}
private static class DateToCalendarConverter implements Converter<Date, Calendar> {
public Calendar convert(Date source) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(source);
return calendar;
}
}
private static class CalendarToDateConverter implements Converter<Calendar, Date> {
public Date convert(Calendar source) {
return source.getTime();
}
}
private static class CalendarToLongConverter implements Converter<Calendar, Long> {
public Long convert(Calendar source) {
return source.getTime().getTime();
}
}
private static class LongToDateConverter implements Converter<Long, Date> {
public Date convert(Long source) {
return new Date(source);
}
}
private static class LongToCalendarConverter implements Converter<Long, Calendar> {
private DateToCalendarConverter dateToCalendarConverter = new DateToCalendarConverter();
public Calendar convert(Long source) {
return dateToCalendarConverter.convert(new Date(source));
}
}
}

View File

@@ -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<DateTimeFormat>, EmbeddedValueResolverAware {
private static final Set<Class<?>> FIELD_TYPES;
static {
Set<Class<?>> fieldTypes = new HashSet<Class<?>>();
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<Class<?>> 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<Date> 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);
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.joda;
import java.util.TimeZone;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.util.StringUtils;
/**
* {@link FactoryBean} that creates a Joda {@link DateTimeFormatter}. Formatters will be
* created using the defined {@link #setPattern(String) pattern}, {@link #setIso(ISO) ISO}
* or {@link #setStyle(String) style} (considered in that order).
*
* @author Phillip Webb
* @see #getDateTimeFormatter()
* @see #getDateTimeFormatter(DateTimeFormatter)
* @since 3.2
*/
public class DateTimeFormatterFactory implements FactoryBean<DateTimeFormatter> {
private ISO iso;
private String style;
private String pattern;
private TimeZone timeZone;
/**
* Create a new {@link DateTimeFormatterFactory} instance.
*/
public DateTimeFormatterFactory() {
}
/**
* Create a new {@link DateTimeFormatterFactory} instance.
* @param pattern the pattern to use to format date values
*/
public DateTimeFormatterFactory(String pattern) {
this.pattern = pattern;
}
public boolean isSingleton() {
return true;
}
public Class<?> getObjectType() {
return DateTimeFormatter.class;
}
public DateTimeFormatter getObject() throws Exception {
return getDateTimeFormatter();
}
/**
* Get a new DateTimeFormatter using this factory. If no specific
* {@link #setStyle(String) style} {@link #setIso(ISO) ISO} or
* {@link #setPattern(String) pattern} have been defined the
* {@link DateTimeFormat#mediumDateTime() medium date time format} will be used.
* @return a new date time formatter
* @see #getObject()
* @see #getDateTimeFormatter(DateTimeFormatter)
*/
public DateTimeFormatter getDateTimeFormatter() {
return getDateTimeFormatter(DateTimeFormat.mediumDateTime());
}
/**
* Get a new DateTimeFormatter using this factory. If no specific
* {@link #setStyle(String) style} {@link #setIso(ISO) ISO} or
* {@link #setPattern(String) pattern} have been defined the specific
* {@code fallbackFormatter} will be used.
* @param fallbackFormatter the fall-back formatter to use when no specific factory
* properties have been set (can be {@code null}).
* @return a new date time formatter
*/
public DateTimeFormatter getDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
DateTimeFormatter dateTimeFormatter = createDateTimeFormatter();
if(dateTimeFormatter != null && this.timeZone != null) {
dateTimeFormatter.withZone(DateTimeZone.forTimeZone(this.timeZone));
}
return (dateTimeFormatter != null ? dateTimeFormatter : fallbackFormatter);
}
private DateTimeFormatter createDateTimeFormatter() {
if (StringUtils.hasLength(pattern)) {
return DateTimeFormat.forPattern(pattern);
}
if (iso != null && iso != ISO.NONE) {
if (iso == ISO.DATE) {
return ISODateTimeFormat.date();
}
if (iso == ISO.TIME) {
return ISODateTimeFormat.time();
}
return ISODateTimeFormat.dateTime();
}
if (StringUtils.hasLength(style)) {
return DateTimeFormat.forStyle(style);
}
return null;
}
/**
* Set the TimeZone to normalize the date values into, if any.
* @param timeZone the time zone
*/
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
/**
* 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
* <ul>
* <li>'S' = Small</li>
* <li>'M' = Medium</li>
* <li>'L' = Long</li>
* <li>'F' = Full</li>
* <li>'-' = Omitted</li>
* <ul>
* This method mimics the styles supported by Joda Time.
* @param style two characters from the set {"S", "M", "L", "F", "-"}
*/
public void setStyle(String style) {
this.style = style;
}
/**
* Set the ISO format used for this date.
* @param iso the iso format
*/
public void setIso(ISO iso) {
this.iso = iso;
}
/**
* Set the pattern to use to format date values.
* @param pattern the format pattern
*/
public void setPattern(String pattern) {
this.pattern = pattern;
}
}

View File

@@ -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.
@@ -34,12 +34,10 @@ import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
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
@@ -49,20 +47,33 @@ import org.springframework.util.StringValueResolver;
public class JodaDateTimeFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<DateTimeFormat>, EmbeddedValueResolverAware {
private final Set<Class<?>> fieldTypes;
private static final Set<Class<?>> FIELD_TYPES;
static {
// Create the set of field types that may be annotated with @DateTimeFormat.
// Note: the 3 ReadablePartial concrete types are registered explicitly since
// addFormatterForFieldType rules exist for each of these types
// (if we did not do this, the default byType rules for LocalDate, LocalTime,
// and LocalDateTime would take precedence over the annotation rule, which
// is not what we want)
Set<Class<?>> fieldTypes = new HashSet<Class<?>>(7);
fieldTypes.add(ReadableInstant.class);
fieldTypes.add(LocalDate.class);
fieldTypes.add(LocalTime.class);
fieldTypes.add(LocalDateTime.class);
fieldTypes.add(Date.class);
fieldTypes.add(Calendar.class);
fieldTypes.add(Long.class);
FIELD_TYPES = Collections.unmodifiableSet(fieldTypes);
}
private StringValueResolver embeddedValueResolver;
public JodaDateTimeFormatAnnotationFormatterFactory() {
this.fieldTypes = createFieldTypes();
}
public final Set<Class<?>> getFieldTypes() {
return this.fieldTypes;
return FIELD_TYPES;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
@@ -71,79 +82,42 @@ public class JodaDateTimeFormatAnnotationFormatterFactory
return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
}
public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
DateTimeFormatter formatter = configureDateTimeFormatterFrom(annotation);
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
if (ReadableInstant.class.isAssignableFrom(fieldType)) {
return new ReadableInstantPrinter(formatter);
}
else if (ReadablePartial.class.isAssignableFrom(fieldType)) {
if (ReadablePartial.class.isAssignableFrom(fieldType)) {
return new ReadablePartialPrinter(formatter);
}
else if (Calendar.class.isAssignableFrom(fieldType)) {
if (Calendar.class.isAssignableFrom(fieldType)) {
// assumes Calendar->ReadableInstant converter is registered
return new ReadableInstantPrinter(formatter);
return new ReadableInstantPrinter(formatter);
}
else {
// assumes Date->Long converter is registered
return new MillisecondInstantPrinter(formatter);
}
// assumes Date->Long converter is registered
return new MillisecondInstantPrinter(formatter);
}
public Parser<DateTime> getParser(DateTimeFormat annotation, Class<?> fieldType) {
return new DateTimeParser(configureDateTimeFormatterFrom(annotation));
return new DateTimeParser(getFormatter(annotation, fieldType));
}
// internal helpers
/**
* Create the set of field types that may be annotated with @DateTimeFormat.
* Note: the 3 ReadablePartial concrete types are registered explicitly since addFormatterForFieldType rules exist for each of these types
* (if we did not do this, the default byType rules for LocalDate, LocalTime, and LocalDateTime would take precedence over the annotation rule, which is not what we want)
* @see JodaTimeFormatterRegistrar#registerFormatters(org.springframework.format.FormatterRegistry)
/**
* Factory method used to create a {@link DateTimeFormatter}.
* @param annotation the format annotation for the field
* @param fieldType the type of field
* @return a {@link DateTimeFormatter} instance
* @since 3.2
*/
private Set<Class<?>> createFieldTypes() {
Set<Class<?>> rawFieldTypes = new HashSet<Class<?>>(7);
rawFieldTypes.add(ReadableInstant.class);
rawFieldTypes.add(LocalDate.class);
rawFieldTypes.add(LocalTime.class);
rawFieldTypes.add(LocalDateTime.class);
rawFieldTypes.add(Date.class);
rawFieldTypes.add(Calendar.class);
rawFieldTypes.add(Long.class);
return Collections.unmodifiableSet(rawFieldTypes);
protected DateTimeFormatter getFormatter(DateTimeFormat annotation, Class<?> fieldType) {
DateTimeFormatterFactory factory = new DateTimeFormatterFactory();
factory.setStyle(resolveEmbeddedValue(annotation.style()));
factory.setIso(annotation.iso());
factory.setPattern(resolveEmbeddedValue(annotation.pattern()));
return factory.getDateTimeFormatter();
}
private DateTimeFormatter configureDateTimeFormatterFrom(DateTimeFormat annotation) {
if (StringUtils.hasLength(annotation.pattern())) {
return forPattern(resolveEmbeddedValue(annotation.pattern()));
}
else if (annotation.iso() != ISO.NONE) {
return forIso(annotation.iso());
}
else {
return forStyle(resolveEmbeddedValue(annotation.style()));
}
}
private DateTimeFormatter forPattern(String pattern) {
return org.joda.time.format.DateTimeFormat.forPattern(pattern);
}
private DateTimeFormatter forStyle(String style) {
return org.joda.time.format.DateTimeFormat.forStyle(style);
}
private DateTimeFormatter forIso(ISO iso) {
if (iso == ISO.DATE) {
return org.joda.time.format.ISODateTimeFormat.date();
}
else if (iso == ISO.TIME) {
return org.joda.time.format.ISODateTimeFormat.time();
}
else {
return org.joda.time.format.ISODateTimeFormat.dateTime();
}
}
}

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.
@@ -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,14 +54,13 @@ final class JodaTimeConverters {
registry.addConverter(new DateTimeToDateConverter());
registry.addConverter(new DateTimeToCalendarConverter());
registry.addConverter(new DateTimeToLongConverter());
registry.addConverter(new DateToLongConverter());
registry.addConverter(new CalendarToReadableInstantConverter());
}
/**
* Used when binding a parsed DateTime to a LocalDate field.
* @see DateTimeParser
* @see DateTimeParser
**/
private static class DateTimeToLocalDateConverter implements Converter<DateTime, LocalDate> {
public LocalDate convert(DateTime source) {
@@ -67,9 +68,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a LocalTime field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a LocalTime field.
* @see DateTimeParser
*/
private static class DateTimeToLocalTimeConverter implements Converter<DateTime, LocalTime> {
public LocalTime convert(DateTime source) {
@@ -77,9 +78,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a LocalDateTime field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a LocalDateTime field.
* @see DateTimeParser
*/
private static class DateTimeToLocalDateTimeConverter implements Converter<DateTime, LocalDateTime> {
public LocalDateTime convert(DateTime source) {
@@ -87,9 +88,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a DateMidnight field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a DateMidnight field.
* @see DateTimeParser
*/
private static class DateTimeToDateMidnightConverter implements Converter<DateTime, DateMidnight> {
public DateMidnight convert(DateTime source) {
@@ -97,9 +98,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to an Instant field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to an Instant field.
* @see DateTimeParser
*/
private static class DateTimeToInstantConverter implements Converter<DateTime, Instant> {
public Instant convert(DateTime source) {
@@ -107,19 +108,19 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a MutableDateTime field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a MutableDateTime field.
* @see DateTimeParser
*/
private static class DateTimeToMutableDateTimeConverter implements Converter<DateTime, MutableDateTime> {
public MutableDateTime convert(DateTime source) {
return source.toMutableDateTime();
}
}
/**
* Used when binding a parsed DateTime to a java.util.Date field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a java.util.Date field.
* @see DateTimeParser
*/
private static class DateTimeToDateConverter implements Converter<DateTime, Date> {
public Date convert(DateTime source) {
@@ -127,9 +128,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a java.util.Calendar field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a java.util.Calendar field.
* @see DateTimeParser
*/
private static class DateTimeToCalendarConverter implements Converter<DateTime, Calendar> {
public Calendar convert(DateTime source) {
@@ -137,9 +138,9 @@ final class JodaTimeConverters {
}
}
/**
* Used when binding a parsed DateTime to a java.lang.Long field.
* @see DateTimeParser
/**
* Used when binding a parsed DateTime to a java.lang.Long field.
* @see DateTimeParser
*/
private static class DateTimeToLongConverter implements Converter<DateTime, Long> {
public Long convert(DateTime source) {
@@ -147,22 +148,11 @@ final class JodaTimeConverters {
}
}
/**
* Used when printing a java.util.Date field with a MillisecondInstantPrinter.
* @see MillisecondInstantPrinter
* @see JodaDateTimeFormatAnnotationFormatterFactory
*/
private static class DateToLongConverter implements Converter<Date, Long> {
public Long convert(Date source) {
return source.getTime();
}
}
/**
/**
* Used when printing a java.util.Calendar field with a ReadableInstantPrinter.
* @see MillisecondInstantPrinter
* @see JodaDateTimeFormatAnnotationFormatterFactory
*/
*/
private static class CalendarToReadableInstantConverter implements Converter<Calendar, ReadableInstant> {
public ReadableInstant convert(Calendar source) {
return new DateTime(source);

View File

@@ -17,6 +17,8 @@ package org.springframework.format.datetime.joda;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -25,40 +27,54 @@ import org.joda.time.LocalTime;
import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.datetime.DateFormatterRegistrar;
/**
* Configures Joda Time's Formatting system for use with Spring.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1
* @see #setDateStyle
* @see #setTimeStyle
* @see #setDateTimeStyle
* @see #setUseIsoFormat
* @see FormatterRegistrar#registerFormatters
* @see DateFormatterRegistrar
*/
public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
private String dateStyle;
/**
* User defined formatters.
*/
private Map<Type, DateTimeFormatter> formatters = new HashMap<Type, DateTimeFormatter>();
private String timeStyle;
/**
* Factories used when specific formatters have not been specified.
*/
private Map<Type, DateTimeFormatterFactory> factories;
private String dateTimeStyle;
private boolean useIsoFormat;
public JodaTimeFormatterRegistrar() {
this.factories = new HashMap<Type, DateTimeFormatterFactory>();
for (Type type : Type.values()) {
this.factories.put(type, new DateTimeFormatterFactory());
}
}
/**
* Set the default format style of Joda {@link LocalDate} objects.
* Default is {@link DateTimeFormat#shortDate()}.
*/
public void setDateStyle(String dateStyle) {
this.dateStyle = dateStyle;
factories.get(Type.DATE).setStyle(dateStyle+"-");
}
/**
@@ -66,7 +82,7 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
* Default is {@link DateTimeFormat#shortTime()}.
*/
public void setTimeStyle(String timeStyle) {
this.timeStyle = timeStyle;
factories.get(Type.TIME).setStyle("-"+timeStyle);
}
/**
@@ -75,7 +91,7 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
* Default is {@link DateTimeFormat#shortDateTime()}.
*/
public void setDateTimeStyle(String dateTimeStyle) {
this.dateTimeStyle = dateTimeStyle;
factories.get(Type.DATE_TIME).setStyle(dateTimeStyle);
}
/**
@@ -84,64 +100,108 @@ public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
* If set to true, the dateStyle, timeStyle, and dateTimeStyle properties are ignored.
*/
public void setUseIsoFormat(boolean useIsoFormat) {
this.useIsoFormat = useIsoFormat;
factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : null);
factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : null);
factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : null);
}
/**
* Set the formatter that will be used for objects representing date values.
* This formatter will be used for the {@link LocalDate} type. When specified
* {@link #setDateStyle(String) dateStyle} and
* {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
* @param formatter the formatter to use
* @see #setTimeFormatter(DateTimeFormatter)
* @see #setDateTimeFormatter(DateTimeFormatter)
* @since 3.2
*/
public void setDateFormatter(DateTimeFormatter formatter) {
this.formatters.put(Type.DATE, formatter);
}
/**
* Set the formatter that will be used for objects representing date values.
* This formatter will be used for the {@link LocalTime} type. When specified
* {@link #setTimeStyle(String) timeStyle} and
* {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
* @param formatter the formatter to use
* @see #setDateFormatter(DateTimeFormatter)
* @see #setDateTimeFormatter(DateTimeFormatter)
* @since 3.2
*/
public void setTimeFormatter(DateTimeFormatter formatter) {
this.formatters.put(Type.TIME, formatter);
}
/**
* Set the formatter that will be used for objects representing date and time values.
* This formatter will be used for {@link LocalDateTime}, {@link ReadableInstant},
* {@link Date} and {@link Calendar} types. When specified
* {@link #setDateTimeStyle(String) dateTimeStyle} and
* {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
* @param formatter the formatter to use
* @see #setDateFormatter(DateTimeFormatter)
* @see #setTimeFormatter(DateTimeFormatter)
* @since 3.2
*/
public void setDateTimeFormatter(DateTimeFormatter formatter) {
this.formatters.put(Type.DATE_TIME, formatter);
}
public void registerFormatters(FormatterRegistry registry) {
JodaTimeConverters.registerConverters(registry);
DateTimeFormatter jodaDateFormatter = getJodaDateFormatter();
registry.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(jodaDateFormatter),
new DateTimeParser(jodaDateFormatter));
DateTimeFormatter dateFormatter = getFormatter(Type.DATE);
DateTimeFormatter timeFormatter = getFormatter(Type.TIME);
DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME);
DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter();
registry.addFormatterForFieldType(LocalTime.class, new ReadablePartialPrinter(jodaTimeFormatter),
new DateTimeParser(jodaTimeFormatter));
addFormatterForFields(registry,
new ReadablePartialPrinter(dateFormatter),
new DateTimeParser(dateFormatter),
LocalDate.class);
DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter();
Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter);
registry.addFormatterForFieldType(LocalDateTime.class, new ReadablePartialPrinter(jodaDateTimeFormatter),
dateTimeParser);
addFormatterForFields(registry,
new ReadablePartialPrinter(timeFormatter),
new DateTimeParser(timeFormatter),
LocalTime.class);
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter);
registry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser);
addFormatterForFields(registry,
new ReadablePartialPrinter(dateTimeFormatter),
new DateTimeParser(dateTimeFormatter),
LocalDateTime.class);
registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
addFormatterForFields(registry,
new ReadableInstantPrinter(dateTimeFormatter),
new DateTimeParser(dateTimeFormatter),
ReadableInstant.class, Date.class, Calendar.class);
registry.addFormatterForFieldAnnotation(
new JodaDateTimeFormatAnnotationFormatterFactory());
}
// internal helpers
private DateTimeFormatter getJodaDateFormatter() {
if (this.useIsoFormat) {
return ISODateTimeFormat.date();
private DateTimeFormatter getFormatter(Type type) {
DateTimeFormatter formatter = formatters.get(type);
if(formatter != null) {
return formatter;
}
if (this.dateStyle != null) {
return DateTimeFormat.forStyle(this.dateStyle + "-");
} else {
return DateTimeFormat.shortDate();
DateTimeFormatter fallbackFormatter = getFallbackFormatter(type);
return factories.get(type).getDateTimeFormatter(fallbackFormatter );
}
private DateTimeFormatter getFallbackFormatter(Type type) {
switch (type) {
case DATE: return DateTimeFormat.shortDate();
case TIME: return DateTimeFormat.shortTime();
default: return DateTimeFormat.shortDateTime();
}
}
private DateTimeFormatter getJodaTimeFormatter() {
if (this.useIsoFormat) {
return ISODateTimeFormat.time();
}
if (this.timeStyle != null) {
return DateTimeFormat.forStyle("-" + this.timeStyle);
} else {
return DateTimeFormat.shortTime();
}
}
private DateTimeFormatter getJodaDateTimeFormatter() {
if (this.useIsoFormat) {
return ISODateTimeFormat.dateTime();
}
if (this.dateTimeStyle != null) {
return DateTimeFormat.forStyle(this.dateTimeStyle);
} else {
return DateTimeFormat.shortDateTime();
private void addFormatterForFields(FormatterRegistry registry, Printer<?> printer,
Parser<?> parser, Class<?>... fieldTypes) {
for (Class<?> fieldType : fieldTypes) {
registry.addFormatterForFieldType(fieldType, printer, parser);
}
}
private static enum Type {DATE, TIME, DATE_TIME}
}

View File

@@ -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<DateTimeFormat> {
private final Set<Class<?>> fieldTypes;
public NoJodaDateTimeFormatAnnotationFormatterFactory() {
Set<Class<?>> rawFieldTypes = new HashSet<Class<?>>(4);
rawFieldTypes.add(Date.class);
rawFieldTypes.add(Calendar.class);
rawFieldTypes.add(Long.class);
this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes);
}
public Set<Class<?>> 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);
}
}

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;
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.joda;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.*;
import java.util.Locale;
import java.util.TimeZone;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.junit.Test;
import org.springframework.format.annotation.DateTimeFormat.ISO;
/**
* Tests for {@link DateTimeFormatterFactory}.
*
* @author Phillip Webb
*/
public class DateTimeFormatterFactoryTests {
private DateTimeFormatterFactory factory = new DateTimeFormatterFactory();
private DateTime dateTime = new DateTime(2009, 10, 21, 12, 10, 00, 00);
@Test
public void shouldDefaultToMediumFormat() throws Exception {
assertThat(factory.getObject(), is(equalTo(DateTimeFormat.mediumDateTime())));
assertThat(factory.getDateTimeFormatter(), is(equalTo(DateTimeFormat.mediumDateTime())));
}
@Test
public void shouldCreateFromPattern() throws Exception {
factory = new DateTimeFormatterFactory("yyyyMMddHHmmss");
DateTimeFormatter formatter = factory.getObject();
assertThat(formatter.print(dateTime), is("20091021121000"));
}
@Test
public void shouldBeSingleton() throws Exception {
assertThat(factory.isSingleton(), is(true));
}
@Test
@SuppressWarnings("rawtypes")
public void shouldCreateDateTimeFormatter() throws Exception {
assertThat(factory.getObjectType(), is(equalTo((Class)DateTimeFormatter.class)));
}
@Test
public void shouldGetDateTimeFormatterNullFallback() throws Exception {
DateTimeFormatter formatter = factory.getDateTimeFormatter(null);
assertThat(formatter, is(nullValue()));
}
@Test
public void shouldGetDateTimeFormatterFallback() throws Exception {
DateTimeFormatter fallback = DateTimeFormat.forStyle("LL");
DateTimeFormatter formatter = factory.getDateTimeFormatter(fallback);
assertThat(formatter, is(sameInstance(fallback)));
}
@Test
public void shouldGetDateTimeFormatter() throws Exception {
factory.setStyle("SS");
assertThat(applyLocale(factory.getDateTimeFormatter()).print(dateTime), is("10/21/09 12:10 PM"));
factory.setIso(ISO.DATE);
assertThat(applyLocale(factory.getDateTimeFormatter()).print(dateTime), is("2009-10-21"));
factory.setPattern("yyyyMMddHHmmss");
assertThat(factory.getDateTimeFormatter().print(dateTime), is("20091021121000"));
}
@Test
public void shouldGetWithTimeZone() throws Exception {
factory.setPattern("yyyyMMddHHmmss Z");
factory.setTimeZone(TimeZone.getTimeZone("-0700"));
assertThat(factory.getDateTimeFormatter().print(dateTime), is("20091021121000 -0700"));
}
private DateTimeFormatter applyLocale(DateTimeFormatter dateTimeFormatter) {
return dateTimeFormatter.withLocale(Locale.US);
}
}

View File

@@ -49,15 +49,20 @@ import static org.junit.Assert.*;
*/
public class JodaTimeFormattingTests {
private FormattingConversionService conversionService = new FormattingConversionService();
private FormattingConversionService conversionService;
private DataBinder binder;
@Before
public void setUp() {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
setUp(registrar);
}
private void setUp(JodaTimeFormatterRegistrar registrar) {
conversionService = new FormattingConversionService();
DefaultConversionService.addDefaultConverters(conversionService);
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.registerFormatters(conversionService);
JodaTimeBean bean = new JodaTimeBean();
@@ -84,7 +89,7 @@ public class JodaTimeFormattingTests {
System.out.println(org.joda.time.format.DateTimeFormat.patternForStyle("LL", LocaleContextHolder.getLocale()));
System.out.println(org.joda.time.format.DateTimeFormat.patternForStyle("FF", LocaleContextHolder.getLocale()));
}
@Test
public void testBindLocalDate() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -94,6 +99,30 @@ public class JodaTimeFormattingTests {
assertEquals("10/31/09", binder.getBindingResult().getFieldValue("localDate"));
}
@Test
public void testBindLocalDateWithSpecificStyle() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setDateStyle("L");
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "October 31, 2009");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("October 31, 2009", binder.getBindingResult().getFieldValue("localDate"));
}
@Test
public void testBindLocalDateWithSpecifcFormatter() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setDateFormatter(org.joda.time.format.DateTimeFormat.forPattern("yyyyMMdd"));
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "20091031");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("20091031", binder.getBindingResult().getFieldValue("localDate"));
}
@Test
public void testBindLocalDateArray() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -158,6 +187,30 @@ public class JodaTimeFormattingTests {
assertEquals("12:00 PM", binder.getBindingResult().getFieldValue("localTime"));
}
@Test
public void testBindLocalTimeWithSpecificStyle() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setTimeStyle("M");
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "12:00:00 PM");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("12:00:00 PM", binder.getBindingResult().getFieldValue("localTime"));
}
@Test
public void testBindLocalTimeWithSpecificFormatter() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setTimeFormatter(org.joda.time.format.DateTimeFormat.forPattern("HHmmss"));
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "130000");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("130000", binder.getBindingResult().getFieldValue("localTime"));
}
@Test
public void testBindLocalTimeAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -205,6 +258,42 @@ public class JodaTimeFormattingTests {
assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("dateTime"));
}
@Test
public void testBindDateTimeWithSpecificStyle() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setDateTimeStyle("MM");
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", "Oct 31, 2009 12:00:00 PM");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("Oct 31, 2009 12:00:00 PM", binder.getBindingResult().getFieldValue("localDateTime"));
}
@Test
public void testBindDateTimeISO() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("dateTime", "2009-10-31T12:00:00.000Z");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("2009-10-31T07:00:00.000-05:00", binder.getBindingResult().getFieldValue("dateTime"));
}
@Test
public void testBindDateTimeWithSpecificFormatter() throws Exception {
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.setDateTimeFormatter(org.joda.time.format.DateTimeFormat.forPattern("yyyyMMddHHmmss"));
setUp(registrar);
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("dateTime", "20091031130000");
binder.bind(propertyValues);
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("20091031130000", binder.getBindingResult().getFieldValue("dateTime"));
}
@Test
public void testBindDateTimeAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -339,7 +428,7 @@ public class JodaTimeFormattingTests {
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("instant"));
}
@Test
public void testBindInstantAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -357,7 +446,7 @@ public class JodaTimeFormattingTests {
assertEquals(0, binder.getBindingResult().getErrorCount());
assertEquals("10/31/09 12:00 PM", binder.getBindingResult().getFieldValue("mutableDateTime"));
}
@Test
public void testBindMutableDateTimeAnnotated() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
@@ -483,7 +572,7 @@ public class JodaTimeFormattingTests {
public void setLocalDateTimeAnnotated(LocalDateTime localDateTimeAnnotated) {
this.localDateTimeAnnotated = localDateTimeAnnotated;
}
public LocalDateTime getLocalDateTimeAnnotatedLong() {
return localDateTimeAnnotatedLong;
}
@@ -596,7 +685,7 @@ public class JodaTimeFormattingTests {
public void setIsoDateTime(DateTime isoDateTime) {
this.isoDateTime = isoDateTime;
}
public Instant getInstant() {
return instant;
}