001/*
002 * Copyright 2002-2018 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.standard;
018
019import java.time.Duration;
020import java.time.Instant;
021import java.time.LocalDate;
022import java.time.LocalDateTime;
023import java.time.LocalTime;
024import java.time.Month;
025import java.time.MonthDay;
026import java.time.OffsetDateTime;
027import java.time.OffsetTime;
028import java.time.Period;
029import java.time.Year;
030import java.time.YearMonth;
031import java.time.ZonedDateTime;
032import java.time.format.DateTimeFormatter;
033import java.time.format.FormatStyle;
034import java.util.EnumMap;
035import java.util.Map;
036
037import org.springframework.format.FormatterRegistrar;
038import org.springframework.format.FormatterRegistry;
039import org.springframework.format.annotation.DateTimeFormat.ISO;
040
041/**
042 * Configures the JSR-310 <code>java.time</code> formatting system for use with Spring.
043 *
044 * @author Juergen Hoeller
045 * @author Phillip Webb
046 * @since 4.0
047 * @see #setDateStyle
048 * @see #setTimeStyle
049 * @see #setDateTimeStyle
050 * @see #setUseIsoFormat
051 * @see org.springframework.format.FormatterRegistrar#registerFormatters
052 * @see org.springframework.format.datetime.DateFormatterRegistrar
053 * @see org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean
054 */
055public class DateTimeFormatterRegistrar implements FormatterRegistrar {
056
057        private enum Type {DATE, TIME, DATE_TIME}
058
059
060        /**
061         * User-defined formatters.
062         */
063        private final Map<Type, DateTimeFormatter> formatters = new EnumMap<>(Type.class);
064
065        /**
066         * Factories used when specific formatters have not been specified.
067         */
068        private final Map<Type, DateTimeFormatterFactory> factories = new EnumMap<>(Type.class);
069
070
071        public DateTimeFormatterRegistrar() {
072                for (Type type : Type.values()) {
073                        this.factories.put(type, new DateTimeFormatterFactory());
074                }
075        }
076
077
078        /**
079         * Set whether standard ISO formatting should be applied to all date/time types.
080         * Default is "false" (no).
081         * <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle"
082         * properties are effectively ignored.
083         */
084        public void setUseIsoFormat(boolean useIsoFormat) {
085                this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : ISO.NONE);
086                this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : ISO.NONE);
087                this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : ISO.NONE);
088        }
089
090        /**
091         * Set the default format style of {@link java.time.LocalDate} objects.
092         * Default is {@link java.time.format.FormatStyle#SHORT}.
093         */
094        public void setDateStyle(FormatStyle dateStyle) {
095                this.factories.get(Type.DATE).setDateStyle(dateStyle);
096        }
097
098        /**
099         * Set the default format style of {@link java.time.LocalTime} objects.
100         * Default is {@link java.time.format.FormatStyle#SHORT}.
101         */
102        public void setTimeStyle(FormatStyle timeStyle) {
103                this.factories.get(Type.TIME).setTimeStyle(timeStyle);
104        }
105
106        /**
107         * Set the default format style of {@link java.time.LocalDateTime} objects.
108         * Default is {@link java.time.format.FormatStyle#SHORT}.
109         */
110        public void setDateTimeStyle(FormatStyle dateTimeStyle) {
111                this.factories.get(Type.DATE_TIME).setDateTimeStyle(dateTimeStyle);
112        }
113
114        /**
115         * Set the formatter that will be used for objects representing date values.
116         * <p>This formatter will be used for the {@link LocalDate} type.
117         * When specified, the {@link #setDateStyle dateStyle} and
118         * {@link #setUseIsoFormat useIsoFormat} properties will be ignored.
119         * @param formatter the formatter to use
120         * @see #setTimeFormatter
121         * @see #setDateTimeFormatter
122         */
123        public void setDateFormatter(DateTimeFormatter formatter) {
124                this.formatters.put(Type.DATE, formatter);
125        }
126
127        /**
128         * Set the formatter that will be used for objects representing time values.
129         * <p>This formatter will be used for the {@link LocalTime} and {@link OffsetTime}
130         * types. When specified, the {@link #setTimeStyle timeStyle} and
131         * {@link #setUseIsoFormat useIsoFormat} properties will be ignored.
132         * @param formatter the formatter to use
133         * @see #setDateFormatter
134         * @see #setDateTimeFormatter
135         */
136        public void setTimeFormatter(DateTimeFormatter formatter) {
137                this.formatters.put(Type.TIME, formatter);
138        }
139
140        /**
141         * Set the formatter that will be used for objects representing date and time values.
142         * <p>This formatter will be used for {@link LocalDateTime}, {@link ZonedDateTime}
143         * and {@link OffsetDateTime} types. When specified, the
144         * {@link #setDateTimeStyle dateTimeStyle} and
145         * {@link #setUseIsoFormat useIsoFormat} properties will be ignored.
146         * @param formatter the formatter to use
147         * @see #setDateFormatter
148         * @see #setTimeFormatter
149         */
150        public void setDateTimeFormatter(DateTimeFormatter formatter) {
151                this.formatters.put(Type.DATE_TIME, formatter);
152        }
153
154
155        @Override
156        public void registerFormatters(FormatterRegistry registry) {
157                DateTimeConverters.registerConverters(registry);
158
159                DateTimeFormatter df = getFormatter(Type.DATE);
160                DateTimeFormatter tf = getFormatter(Type.TIME);
161                DateTimeFormatter dtf = getFormatter(Type.DATE_TIME);
162
163                // Efficient ISO_LOCAL_* variants for printing since they are twice as fast...
164
165                registry.addFormatterForFieldType(LocalDate.class,
166                                new TemporalAccessorPrinter(
167                                                df == DateTimeFormatter.ISO_DATE ? DateTimeFormatter.ISO_LOCAL_DATE : df),
168                                new TemporalAccessorParser(LocalDate.class, df));
169
170                registry.addFormatterForFieldType(LocalTime.class,
171                                new TemporalAccessorPrinter(
172                                                tf == DateTimeFormatter.ISO_TIME ? DateTimeFormatter.ISO_LOCAL_TIME : tf),
173                                new TemporalAccessorParser(LocalTime.class, tf));
174
175                registry.addFormatterForFieldType(LocalDateTime.class,
176                                new TemporalAccessorPrinter(
177                                                dtf == DateTimeFormatter.ISO_DATE_TIME ? DateTimeFormatter.ISO_LOCAL_DATE_TIME : dtf),
178                                new TemporalAccessorParser(LocalDateTime.class, dtf));
179
180                registry.addFormatterForFieldType(ZonedDateTime.class,
181                                new TemporalAccessorPrinter(dtf),
182                                new TemporalAccessorParser(ZonedDateTime.class, dtf));
183
184                registry.addFormatterForFieldType(OffsetDateTime.class,
185                                new TemporalAccessorPrinter(dtf),
186                                new TemporalAccessorParser(OffsetDateTime.class, dtf));
187
188                registry.addFormatterForFieldType(OffsetTime.class,
189                                new TemporalAccessorPrinter(tf),
190                                new TemporalAccessorParser(OffsetTime.class, tf));
191
192                registry.addFormatterForFieldType(Instant.class, new InstantFormatter());
193                registry.addFormatterForFieldType(Period.class, new PeriodFormatter());
194                registry.addFormatterForFieldType(Duration.class, new DurationFormatter());
195                registry.addFormatterForFieldType(Year.class, new YearFormatter());
196                registry.addFormatterForFieldType(Month.class, new MonthFormatter());
197                registry.addFormatterForFieldType(YearMonth.class, new YearMonthFormatter());
198                registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter());
199
200                registry.addFormatterForFieldAnnotation(new Jsr310DateTimeFormatAnnotationFormatterFactory());
201        }
202
203        private DateTimeFormatter getFormatter(Type type) {
204                DateTimeFormatter formatter = this.formatters.get(type);
205                if (formatter != null) {
206                        return formatter;
207                }
208                DateTimeFormatter fallbackFormatter = getFallbackFormatter(type);
209                return this.factories.get(type).createDateTimeFormatter(fallbackFormatter);
210        }
211
212        private DateTimeFormatter getFallbackFormatter(Type type) {
213                switch (type) {
214                        case DATE: return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
215                        case TIME: return DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
216                        default: return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
217                }
218        }
219
220}