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;
041
042/**
043 * Configures Joda-Time's formatting system for use with Spring.
044 *
045 * <p><b>NOTE:</b> Spring's Joda-Time support requires Joda-Time 2.x, as of Spring 4.0.
046 *
047 * @author Keith Donald
048 * @author Juergen Hoeller
049 * @author Phillip Webb
050 * @since 3.1
051 * @see #setDateStyle
052 * @see #setTimeStyle
053 * @see #setDateTimeStyle
054 * @see #setUseIsoFormat
055 * @see FormatterRegistrar#registerFormatters
056 * @see org.springframework.format.datetime.DateFormatterRegistrar
057 * @see DateTimeFormatterFactoryBean
058 */
059public class JodaTimeFormatterRegistrar implements FormatterRegistrar {
060
061        private enum Type {DATE, TIME, DATE_TIME}
062
063
064        /**
065         * User defined formatters.
066         */
067        private final Map<Type, DateTimeFormatter> formatters = new EnumMap<>(Type.class);
068
069        /**
070         * Factories used when specific formatters have not been specified.
071         */
072        private final Map<Type, DateTimeFormatterFactory> factories;
073
074
075        public JodaTimeFormatterRegistrar() {
076                this.factories = new EnumMap<>(Type.class);
077                for (Type type : Type.values()) {
078                        this.factories.put(type, new DateTimeFormatterFactory());
079                }
080        }
081
082
083        /**
084         * Set whether standard ISO formatting should be applied to all date/time types.
085         * Default is "false" (no).
086         * <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle"
087         * properties are effectively ignored.
088         */
089        public void setUseIsoFormat(boolean useIsoFormat) {
090                this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : ISO.NONE);
091                this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : ISO.NONE);
092                this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : ISO.NONE);
093        }
094
095        /**
096         * Set the default format style of Joda {@link LocalDate} objects.
097         * Default is {@link DateTimeFormat#shortDate()}.
098         */
099        public void setDateStyle(String dateStyle) {
100                this.factories.get(Type.DATE).setStyle(dateStyle + "-");
101        }
102
103        /**
104         * Set the default format style of Joda {@link LocalTime} objects.
105         * Default is {@link DateTimeFormat#shortTime()}.
106         */
107        public void setTimeStyle(String timeStyle) {
108                this.factories.get(Type.TIME).setStyle("-" + timeStyle);
109        }
110
111        /**
112         * Set the default format style of Joda {@link LocalDateTime} and {@link DateTime} objects,
113         * as well as JDK {@link Date} and {@link Calendar} objects.
114         * Default is {@link DateTimeFormat#shortDateTime()}.
115         */
116        public void setDateTimeStyle(String dateTimeStyle) {
117                this.factories.get(Type.DATE_TIME).setStyle(dateTimeStyle);
118        }
119
120        /**
121         * Set the formatter that will be used for objects representing date values.
122         * <p>This formatter will be used for the {@link LocalDate} type. When specified
123         * the {@link #setDateStyle(String) dateStyle} and
124         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
125         * @param formatter the formatter to use
126         * @since 3.2
127         * @see #setTimeFormatter
128         * @see #setDateTimeFormatter
129         */
130        public void setDateFormatter(DateTimeFormatter formatter) {
131                this.formatters.put(Type.DATE, formatter);
132        }
133
134        /**
135         * Set the formatter that will be used for objects representing time values.
136         * <p>This formatter will be used for the {@link LocalTime} type. When specified
137         * the {@link #setTimeStyle(String) timeStyle} and
138         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
139         * @param formatter the formatter to use
140         * @since 3.2
141         * @see #setDateFormatter
142         * @see #setDateTimeFormatter
143         */
144        public void setTimeFormatter(DateTimeFormatter formatter) {
145                this.formatters.put(Type.TIME, formatter);
146        }
147
148        /**
149         * Set the formatter that will be used for objects representing date and time values.
150         * <p>This formatter will be used for {@link LocalDateTime}, {@link ReadableInstant},
151         * {@link Date} and {@link Calendar} types. When specified
152         * the {@link #setDateTimeStyle(String) dateTimeStyle} and
153         * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored.
154         * @param formatter the formatter to use
155         * @since 3.2
156         * @see #setDateFormatter
157         * @see #setTimeFormatter
158         */
159        public void setDateTimeFormatter(DateTimeFormatter formatter) {
160                this.formatters.put(Type.DATE_TIME, formatter);
161        }
162
163
164        @Override
165        public void registerFormatters(FormatterRegistry registry) {
166                JodaTimeConverters.registerConverters(registry);
167
168                DateTimeFormatter dateFormatter = getFormatter(Type.DATE);
169                DateTimeFormatter timeFormatter = getFormatter(Type.TIME);
170                DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME);
171
172                addFormatterForFields(registry,
173                                new ReadablePartialPrinter(dateFormatter),
174                                new LocalDateParser(dateFormatter),
175                                LocalDate.class);
176
177                addFormatterForFields(registry,
178                                new ReadablePartialPrinter(timeFormatter),
179                                new LocalTimeParser(timeFormatter),
180                                LocalTime.class);
181
182                addFormatterForFields(registry,
183                                new ReadablePartialPrinter(dateTimeFormatter),
184                                new LocalDateTimeParser(dateTimeFormatter),
185                                LocalDateTime.class);
186
187                addFormatterForFields(registry,
188                                new ReadableInstantPrinter(dateTimeFormatter),
189                                new DateTimeParser(dateTimeFormatter),
190                                ReadableInstant.class);
191
192                // In order to retain backwards compatibility we only register Date/Calendar
193                // types when a user defined formatter is specified (see SPR-10105)
194                if (this.formatters.containsKey(Type.DATE_TIME)) {
195                        addFormatterForFields(registry,
196                                        new ReadableInstantPrinter(dateTimeFormatter),
197                                        new DateTimeParser(dateTimeFormatter),
198                                        Date.class, Calendar.class);
199                }
200
201                registry.addFormatterForFieldType(Period.class, new PeriodFormatter());
202                registry.addFormatterForFieldType(Duration.class, new DurationFormatter());
203                registry.addFormatterForFieldType(YearMonth.class, new YearMonthFormatter());
204                registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter());
205
206                registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory());
207        }
208
209        private DateTimeFormatter getFormatter(Type type) {
210                DateTimeFormatter formatter = this.formatters.get(type);
211                if (formatter != null) {
212                        return formatter;
213                }
214                DateTimeFormatter fallbackFormatter = getFallbackFormatter(type);
215                return this.factories.get(type).createDateTimeFormatter(fallbackFormatter);
216        }
217
218        private DateTimeFormatter getFallbackFormatter(Type type) {
219                switch (type) {
220                        case DATE: return DateTimeFormat.shortDate();
221                        case TIME: return DateTimeFormat.shortTime();
222                        default: return DateTimeFormat.shortDateTime();
223                }
224        }
225
226        private void addFormatterForFields(FormatterRegistry registry, Printer<?> printer,
227                        Parser<?> parser, Class<?>... fieldTypes) {
228
229                for (Class<?> fieldType : fieldTypes) {
230                        registry.addFormatterForFieldType(fieldType, printer, parser);
231                }
232        }
233
234}