Skip to content

Commit 6660227

Browse files
committed
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
1 parent 856fb2c commit 6660227

File tree

6 files changed

+516
-121
lines changed

6 files changed

+516
-121
lines changed

spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class DateFormatterRegistrar implements FormatterRegistrar {
4747
public void registerFormatters(FormatterRegistry registry) {
4848
addDateConverters(registry);
4949
registry.addFormatter(dateFormatter);
50+
registry.addFormatterForFieldType(Calendar.class, dateFormatter);
5051
registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
5152
}
5253

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.format.datetime.joda;
18+
19+
import java.util.TimeZone;
20+
21+
import org.joda.time.DateTimeZone;
22+
import org.joda.time.format.DateTimeFormat;
23+
import org.joda.time.format.DateTimeFormatter;
24+
import org.joda.time.format.ISODateTimeFormat;
25+
import org.springframework.beans.factory.FactoryBean;
26+
import org.springframework.format.annotation.DateTimeFormat.ISO;
27+
import org.springframework.util.StringUtils;
28+
29+
/**
30+
* {@link FactoryBean} that creates a Joda {@link DateTimeFormatter}. Formatters will be
31+
* created using the defined {@link #setPattern(String) pattern}, {@link #setIso(ISO) ISO}
32+
* or {@link #setStyle(String) style} (considered in that order).
33+
*
34+
* @author Phillip Webb
35+
* @see #getDateTimeFormatter()
36+
* @see #getDateTimeFormatter(DateTimeFormatter)
37+
* @since 3.2
38+
*/
39+
public class DateTimeFormatterFactory implements FactoryBean<DateTimeFormatter> {
40+
41+
private ISO iso;
42+
43+
private String style;
44+
45+
private String pattern;
46+
47+
private TimeZone timeZone;
48+
49+
50+
/**
51+
* Create a new {@link DateTimeFormatterFactory} instance.
52+
*/
53+
public DateTimeFormatterFactory() {
54+
}
55+
56+
/**
57+
* Create a new {@link DateTimeFormatterFactory} instance.
58+
* @param pattern the pattern to use to format date values
59+
*/
60+
public DateTimeFormatterFactory(String pattern) {
61+
this.pattern = pattern;
62+
}
63+
64+
65+
public boolean isSingleton() {
66+
return true;
67+
}
68+
69+
public Class<?> getObjectType() {
70+
return DateTimeFormatter.class;
71+
}
72+
73+
public DateTimeFormatter getObject() throws Exception {
74+
return getDateTimeFormatter();
75+
}
76+
77+
/**
78+
* Get a new DateTimeFormatter using this factory. If no specific
79+
* {@link #setStyle(String) style} {@link #setIso(ISO) ISO} or
80+
* {@link #setPattern(String) pattern} have been defined the
81+
* {@link DateTimeFormat#mediumDateTime() medium date time format} will be used.
82+
* @return a new date time formatter
83+
* @see #getObject()
84+
* @see #getDateTimeFormatter(DateTimeFormatter)
85+
*/
86+
public DateTimeFormatter getDateTimeFormatter() {
87+
return getDateTimeFormatter(DateTimeFormat.mediumDateTime());
88+
}
89+
90+
/**
91+
* Get a new DateTimeFormatter using this factory. If no specific
92+
* {@link #setStyle(String) style} {@link #setIso(ISO) ISO} or
93+
* {@link #setPattern(String) pattern} have been defined the specific
94+
* {@code fallbackFormatter} will be used.
95+
* @param fallbackFormatter the fall-back formatter to use when no specific factory
96+
* properties have been set (can be {@code null}).
97+
* @return a new date time formatter
98+
*/
99+
public DateTimeFormatter getDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
100+
DateTimeFormatter dateTimeFormatter = createDateTimeFormatter();
101+
if(dateTimeFormatter != null && this.timeZone != null) {
102+
dateTimeFormatter.withZone(DateTimeZone.forTimeZone(this.timeZone));
103+
}
104+
return (dateTimeFormatter != null ? dateTimeFormatter : fallbackFormatter);
105+
}
106+
107+
private DateTimeFormatter createDateTimeFormatter() {
108+
if (StringUtils.hasLength(pattern)) {
109+
return DateTimeFormat.forPattern(pattern);
110+
}
111+
if (iso != null && iso != ISO.NONE) {
112+
if (iso == ISO.DATE) {
113+
return ISODateTimeFormat.date();
114+
}
115+
if (iso == ISO.TIME) {
116+
return ISODateTimeFormat.time();
117+
}
118+
return ISODateTimeFormat.dateTime();
119+
}
120+
if (StringUtils.hasLength(style)) {
121+
return DateTimeFormat.forStyle(style);
122+
}
123+
return null;
124+
}
125+
126+
127+
/**
128+
* Set the TimeZone to normalize the date values into, if any.
129+
* @param timeZone the time zone
130+
*/
131+
public void setTimeZone(TimeZone timeZone) {
132+
this.timeZone = timeZone;
133+
}
134+
135+
/**
136+
* Set the two character to use to format date values. The first character used for
137+
* the date style, the second is for the time style. Supported characters are
138+
* <ul>
139+
* <li>'S' = Small</li>
140+
* <li>'M' = Medium</li>
141+
* <li>'L' = Long</li>
142+
* <li>'F' = Full</li>
143+
* <li>'-' = Omitted</li>
144+
* <ul>
145+
* This method mimics the styles supported by Joda Time.
146+
* @param style two characters from the set {"S", "M", "L", "F", "-"}
147+
*/
148+
public void setStyle(String style) {
149+
this.style = style;
150+
}
151+
152+
/**
153+
* Set the ISO format used for this date.
154+
* @param iso the iso format
155+
*/
156+
public void setIso(ISO iso) {
157+
this.iso = iso;
158+
}
159+
160+
/**
161+
* Set the pattern to use to format date values.
162+
* @param pattern the format pattern
163+
*/
164+
public void setPattern(String pattern) {
165+
this.pattern = pattern;
166+
}
167+
}

spring-context/src/main/java/org/springframework/format/datetime/joda/JodaDateTimeFormatAnnotationFormatterFactory.java

Lines changed: 41 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
import org.springframework.format.Parser;
3535
import org.springframework.format.Printer;
3636
import org.springframework.format.annotation.DateTimeFormat;
37-
import org.springframework.format.annotation.DateTimeFormat.ISO;
38-
import org.springframework.util.StringUtils;
3937
import org.springframework.util.StringValueResolver;
4038

4139
/**
@@ -49,18 +47,31 @@
4947
public class JodaDateTimeFormatAnnotationFormatterFactory
5048
implements AnnotationFormatterFactory<DateTimeFormat>, EmbeddedValueResolverAware {
5149

52-
private final Set<Class<?>> fieldTypes;
53-
54-
private StringValueResolver embeddedValueResolver;
50+
private static final Set<Class<?>> FIELD_TYPES;
51+
static {
52+
// Create the set of field types that may be annotated with @DateTimeFormat.
53+
// Note: the 3 ReadablePartial concrete types are registered explicitly since
54+
// addFormatterForFieldType rules exist for each of these types
55+
// (if we did not do this, the default byType rules for LocalDate, LocalTime,
56+
// and LocalDateTime would take precedence over the annotation rule, which
57+
// is not what we want)
58+
Set<Class<?>> fieldTypes = new HashSet<Class<?>>(7);
59+
fieldTypes.add(ReadableInstant.class);
60+
fieldTypes.add(LocalDate.class);
61+
fieldTypes.add(LocalTime.class);
62+
fieldTypes.add(LocalDateTime.class);
63+
fieldTypes.add(Date.class);
64+
fieldTypes.add(Calendar.class);
65+
fieldTypes.add(Long.class);
66+
FIELD_TYPES = Collections.unmodifiableSet(fieldTypes);
67+
}
5568

5669

57-
public JodaDateTimeFormatAnnotationFormatterFactory() {
58-
this.fieldTypes = createFieldTypes();
59-
}
70+
private StringValueResolver embeddedValueResolver;
6071

6172

6273
public final Set<Class<?>> getFieldTypes() {
63-
return this.fieldTypes;
74+
return FIELD_TYPES;
6475
}
6576

6677
public void setEmbeddedValueResolver(StringValueResolver resolver) {
@@ -72,77 +83,41 @@ protected String resolveEmbeddedValue(String value) {
7283
}
7384

7485
public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
75-
DateTimeFormatter formatter = configureDateTimeFormatterFrom(annotation);
86+
DateTimeFormatter formatter = getFormatter(annotation, fieldType);
87+
7688
if (ReadableInstant.class.isAssignableFrom(fieldType)) {
7789
return new ReadableInstantPrinter(formatter);
7890
}
79-
else if (ReadablePartial.class.isAssignableFrom(fieldType)) {
91+
92+
if (ReadablePartial.class.isAssignableFrom(fieldType)) {
8093
return new ReadablePartialPrinter(formatter);
8194
}
82-
else if (Calendar.class.isAssignableFrom(fieldType)) {
95+
96+
if (Calendar.class.isAssignableFrom(fieldType)) {
8397
// assumes Calendar->ReadableInstant converter is registered
8498
return new ReadableInstantPrinter(formatter);
8599
}
86-
else {
87-
// assumes Date->Long converter is registered
88-
return new MillisecondInstantPrinter(formatter);
89-
}
100+
101+
// assumes Date->Long converter is registered
102+
return new MillisecondInstantPrinter(formatter);
90103
}
91104

92105
public Parser<DateTime> getParser(DateTimeFormat annotation, Class<?> fieldType) {
93-
return new DateTimeParser(configureDateTimeFormatterFrom(annotation));
106+
return new DateTimeParser(getFormatter(annotation, fieldType));
94107
}
95108

96-
// internal helpers
97-
98109
/**
99-
* Create the set of field types that may be annotated with @DateTimeFormat.
100-
* Note: the 3 ReadablePartial concrete types are registered explicitly since addFormatterForFieldType rules exist for each of these types
101-
* (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)
102-
* @see JodaTimeFormatterRegistrar#registerFormatters(org.springframework.format.FormatterRegistry)
110+
* Factory method used to create a {@link DateTimeFormatter}.
111+
* @param annotation the format annotation for the field
112+
* @param fieldType the type of field
113+
* @return a {@link DateTimeFormatter} instance
114+
* @since 3.2
103115
*/
104-
private Set<Class<?>> createFieldTypes() {
105-
Set<Class<?>> rawFieldTypes = new HashSet<Class<?>>(7);
106-
rawFieldTypes.add(ReadableInstant.class);
107-
rawFieldTypes.add(LocalDate.class);
108-
rawFieldTypes.add(LocalTime.class);
109-
rawFieldTypes.add(LocalDateTime.class);
110-
rawFieldTypes.add(Date.class);
111-
rawFieldTypes.add(Calendar.class);
112-
rawFieldTypes.add(Long.class);
113-
return Collections.unmodifiableSet(rawFieldTypes);
114-
}
115-
116-
private DateTimeFormatter configureDateTimeFormatterFrom(DateTimeFormat annotation) {
117-
if (StringUtils.hasLength(annotation.pattern())) {
118-
return forPattern(resolveEmbeddedValue(annotation.pattern()));
119-
}
120-
else if (annotation.iso() != ISO.NONE) {
121-
return forIso(annotation.iso());
122-
}
123-
else {
124-
return forStyle(resolveEmbeddedValue(annotation.style()));
125-
}
126-
}
127-
128-
private DateTimeFormatter forPattern(String pattern) {
129-
return org.joda.time.format.DateTimeFormat.forPattern(pattern);
130-
}
131-
132-
private DateTimeFormatter forStyle(String style) {
133-
return org.joda.time.format.DateTimeFormat.forStyle(style);
116+
protected DateTimeFormatter getFormatter(DateTimeFormat annotation, Class<?> fieldType) {
117+
DateTimeFormatterFactory factory = new DateTimeFormatterFactory();
118+
factory.setStyle(resolveEmbeddedValue(annotation.style()));
119+
factory.setIso(annotation.iso());
120+
factory.setPattern(resolveEmbeddedValue(annotation.pattern()));
121+
return factory.getDateTimeFormatter();
134122
}
135-
136-
private DateTimeFormatter forIso(ISO iso) {
137-
if (iso == ISO.DATE) {
138-
return org.joda.time.format.ISODateTimeFormat.date();
139-
}
140-
else if (iso == ISO.TIME) {
141-
return org.joda.time.format.ISODateTimeFormat.time();
142-
}
143-
else {
144-
return org.joda.time.format.ISODateTimeFormat.dateTime();
145-
}
146-
}
147-
148123
}

0 commit comments

Comments
 (0)