001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.format.datetime.joda;
018
019import java.util.Calendar;
020import java.util.Date;
021import java.util.EnumMap;
022import java.util.Map;
023
024import org.joda.time.DateTime;
025import org.joda.time.Duration;
026import org.joda.time.LocalDate;
027import org.joda.time.LocalDateTime;
028import org.joda.time.LocalTime;
029import org.joda.time.MonthDay;
030import org.joda.time.Period;
031import org.joda.time.ReadableInstant;
032import org.joda.time.YearMonth;
033import org.joda.time.format.DateTimeFormat;
034import org.joda.time.format.DateTimeFormatter;
035
036import org.springframework.format.FormatterRegistrar;
037import org.springframework.format.FormatterRegistry;
038import org.springframework.format.Parser;
039import org.springframework.format.Printer;
040import org.springframework.format.annotation.DateTimeFormat.ISO;
041import org.springframework.util.ClassUtils;
042
043/**
044 * Configures Joda-Time's formatting system for use with Spring.
045 *
046 * <p><b>NOTE:</b> Spring's Joda-Time support requires Joda-Time 2.x, as of Spring 4.0.
047 *
048 * @author Keith Donald
049 * @author Juergen Hoeller
050 * @author Phillip Webb
051 * @since 3.1
052 * @see #setDateStyle
053 * @see #setTimeStyle
054 * @see #setDateTimeStyle
055 * @see #setUseIsoFormat
056 * @see FormatterRegistrar#registerFormatters
057 * @see org.springframework.format.datetime.DateFormatterRegistrar
058 * @see DateTimeFormatterFactoryBean
059 */
060public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
061
062        private enum Type {DATE, TIME, DATE_TIME}
063
064
065        /**
066         * Strictly speaking, this should not be necessary since we formally require JodaTime 2.x.
067         * However, since Joda-Time formatters are being registered automatically, we defensively
068         * adapt to Joda-Time 1.x when encountered on the classpath. To be removed in Spring 5.0.
069         */
070        private static final boolean jodaTime2Available = ClassUtils.isPresent(
071                        "org.joda.time.YearMonth", JodaTimeFormatterRegistrar.class.getClassLoader());
072
073        /**
074         * User defined formatters.
075         */
076        private final Map<Type, DateTimeFormatter> formatters = new EnumMap<Type, DateTimeFormatter>(Type.class);
077
078        /**
079         * Factories used when specific formatters have not been specified.
080         */
081        private final Map<Type, DateTimeFormatterFactory> factories;
082
083
084        public JodaTimeFormatterRegistrar() {
085                this.factories = new EnumMap<Type, DateTimeFormatterFactory>(Type.class);
086                for (Type type : Type.values()) {
087                        this.factories.put(type, new DateTimeFormatterFactory());
088                }
089        }
090
091
092        /**
093         * Set whether standard ISO formatting should be applied to all date/time types.
094         * Default is "false" (no).
095         * <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle"
096         * properties are effectively ignored.
097         */
098        public void setUseIsoFormat(boolean useIsoFormat) {
099                this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : null);
100                this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : null);
101                this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : null);
102        }
103
104        /**
105         * Set the default format style of Joda {@link LocalDate} objects.
106         * Default is {@link DateTimeFormat#shortDate()}.
107         */
108        public void setDateStyle(String dateStyle) {
109                this.factories.get(Type.DATE).setStyle(dateStyle + "-");
110        }
111
112        /**
113         * Set the default format style of Joda {@link LocalTime} objects.
114         * Default is {@link DateTimeFormat#shortTime()}.
115         */
116        public void setTimeStyle(String timeStyle) {
117                this.factories.get(Type.TIME).setStyle("-" + timeStyle);
118        }
119
120        /**
121         * Set the default format style of Joda {@link LocalDateTime} and {@link DateTime} objects,
122         * as well as JDK {@link Date} and {@link Calendar} objects.
123         * Default is {@link DateTimeFormat#shortDateTime()}.
124         */
125        public void setDateTimeStyle(String dateTimeStyle) {
126                this.factories.get(Type.DATE_TIME).setStyle(dateTimeStyle);
127        }
128
129        /**
130         * Set the formatter that will be used for objects representing date values.
131         * <p>This formatter will be used for the {@link LocalDate} type. When specified
132         * the {@link #setDateStyle(String) dateStyle} and
133         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
134         * @param formatter the formatter to use
135         * @since 3.2
136         * @see #setTimeFormatter
137         * @see #setDateTimeFormatter
138         */
139        public void setDateFormatter(DateTimeFormatter formatter) {
140                this.formatters.put(Type.DATE, formatter);
141        }
142
143        /**
144         * Set the formatter that will be used for objects representing time values.
145         * <p>This formatter will be used for the {@link LocalTime} type. When specified
146         * the {@link #setTimeStyle(String) timeStyle} and
147         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
148         * @param formatter the formatter to use
149         * @since 3.2
150         * @see #setDateFormatter
151         * @see #setDateTimeFormatter
152         */
153        public void setTimeFormatter(DateTimeFormatter formatter) {
154                this.formatters.put(Type.TIME, formatter);
155        }
156
157        /**
158         * Set the formatter that will be used for objects representing date and time values.
159         * <p>This formatter will be used for {@link LocalDateTime}, {@link ReadableInstant},
160         * {@link Date} and {@link Calendar} types. When specified
161         * the {@link #setDateTimeStyle(String) dateTimeStyle} and
162         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
163         * @param formatter the formatter to use
164         * @since 3.2
165         * @see #setDateFormatter
166         * @see #setTimeFormatter
167         */
168        public void setDateTimeFormatter(DateTimeFormatter formatter) {
169                this.formatters.put(Type.DATE_TIME, formatter);
170        }
171
172
173        @Override
174        public void registerFormatters(FormatterRegistry registry) {
175                JodaTimeConverters.registerConverters(registry);
176
177                DateTimeFormatter dateFormatter = getFormatter(Type.DATE);
178                DateTimeFormatter timeFormatter = getFormatter(Type.TIME);
179                DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME);
180
181                addFormatterForFields(registry,
182                                new ReadablePartialPrinter(dateFormatter),
183                                new LocalDateParser(dateFormatter),
184                                LocalDate.class);
185
186                addFormatterForFields(registry,
187                                new ReadablePartialPrinter(timeFormatter),
188                                new LocalTimeParser(timeFormatter),
189                                LocalTime.class);
190
191                addFormatterForFields(registry,
192                                new ReadablePartialPrinter(dateTimeFormatter),
193                                new LocalDateTimeParser(dateTimeFormatter),
194                                LocalDateTime.class);
195
196                addFormatterForFields(registry,
197                                new ReadableInstantPrinter(dateTimeFormatter),
198                                new DateTimeParser(dateTimeFormatter),
199                                ReadableInstant.class);
200
201                // In order to retain backwards compatibility we only register Date/Calendar
202                // types when a user defined formatter is specified (see SPR-10105)
203                if (this.formatters.containsKey(Type.DATE_TIME)) {
204                        addFormatterForFields(registry,
205                                        new ReadableInstantPrinter(dateTimeFormatter),
206                                        new DateTimeParser(dateTimeFormatter),
207                                        Date.class, Calendar.class);
208                }
209
210                registry.addFormatterForFieldType(Period.class, new PeriodFormatter());
211                registry.addFormatterForFieldType(Duration.class, new DurationFormatter());
212                if (jodaTime2Available) {
213                        JodaTime2Delegate.registerAdditionalFormatters(registry);
214                }
215
216                registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
217        }
218
219        private DateTimeFormatter getFormatter(Type type) {
220                DateTimeFormatter formatter = this.formatters.get(type);
221                if (formatter != null) {
222                        return formatter;
223                }
224                DateTimeFormatter fallbackFormatter = getFallbackFormatter(type);
225                return this.factories.get(type).createDateTimeFormatter(fallbackFormatter);
226        }
227
228        private DateTimeFormatter getFallbackFormatter(Type type) {
229                switch (type) {
230                        case DATE: return DateTimeFormat.shortDate();
231                        case TIME: return DateTimeFormat.shortTime();
232                        default: return DateTimeFormat.shortDateTime();
233                }
234        }
235
236        private void addFormatterForFields(FormatterRegistry registry, Printer<?> printer,
237                        Parser<?> parser, Class<?>... fieldTypes) {
238
239                for (Class<?> fieldType : fieldTypes) {
240                        registry.addFormatterForFieldType(fieldType, printer, parser);
241                }
242        }
243
244
245        /**
246         * Inner class to avoid a hard dependency on Joda-Time 2.x.
247         */
248        private static class JodaTime2Delegate {
249
250                public static void registerAdditionalFormatters(FormatterRegistry registry) {
251                        registry.addFormatterForFieldType(YearMonth.class, new YearMonthFormatter());
252                        registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter());
253                }
254        }
255
256}