Support for custom global Joda DateTimeFormatters

Added dateFormatter, timeFormatter and dateTimeFormatter properties
to JodaTimeFormatterRegistrar allowing for custom global formatting.

DateTimeFormatterFactory can be used when configuring with XML.

Issue: SPR-7121
This commit is contained in:
Phillip Webb
2012-10-08 17:59:13 -07:00
parent 856fb2ccac
commit 6660227d22
6 changed files with 518 additions and 123 deletions

View File

@@ -47,6 +47,7 @@ public class DateFormatterRegistrar implements FormatterRegistrar {
public void registerFormatters(FormatterRegistry registry) {
addDateConverters(registry);
registry.addFormatter(dateFormatter);
registry.addFormatterForFieldType(Calendar.class, dateFormatter);
registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
}

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

@@ -34,8 +34,6 @@ 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;
/**
@@ -49,18 +47,31 @@ 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) {
@@ -72,77 +83,41 @@ public class JodaDateTimeFormatAnnotationFormatterFactory
}
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);
}
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

@@ -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,17 +27,19 @@ 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
@@ -46,20 +50,31 @@ import org.springframework.format.Printer;
*/
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+"-");
}
/**
@@ -67,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);
}
/**
@@ -76,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);
}
/**
@@ -85,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}
}