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;
018
019import java.text.DateFormat;
020import java.text.ParseException;
021import java.text.SimpleDateFormat;
022import java.util.Collections;
023import java.util.Date;
024import java.util.EnumMap;
025import java.util.Locale;
026import java.util.Map;
027import java.util.TimeZone;
028
029import org.springframework.format.Formatter;
030import org.springframework.format.annotation.DateTimeFormat.ISO;
031import org.springframework.util.StringUtils;
032
033/**
034 * A formatter for {@link java.util.Date} types.
035 * Allows the configuration of an explicit date pattern and locale.
036 *
037 * @author Keith Donald
038 * @author Juergen Hoeller
039 * @author Phillip Webb
040 * @since 3.0
041 * @see SimpleDateFormat
042 */
043public class DateFormatter implements Formatter<Date> {
044
045        private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
046
047        private static final Map<ISO, String> ISO_PATTERNS;
048
049        static {
050                Map<ISO, String> formats = new EnumMap<ISO, String>(ISO.class);
051                formats.put(ISO.DATE, "yyyy-MM-dd");
052                formats.put(ISO.TIME, "HH:mm:ss.SSSZ");
053                formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
054                ISO_PATTERNS = Collections.unmodifiableMap(formats);
055        }
056
057
058        private String pattern;
059
060        private int style = DateFormat.DEFAULT;
061
062        private String stylePattern;
063
064        private ISO iso;
065
066        private TimeZone timeZone;
067
068        private boolean lenient = false;
069
070
071        /**
072         * Create a new default DateFormatter.
073         */
074        public DateFormatter() {
075        }
076
077        /**
078         * Create a new DateFormatter for the given date pattern.
079         */
080        public DateFormatter(String pattern) {
081                this.pattern = pattern;
082        }
083
084
085        /**
086         * Set the pattern to use to format date values.
087         * <p>If not specified, DateFormat's default style will be used.
088         */
089        public void setPattern(String pattern) {
090                this.pattern = pattern;
091        }
092
093        /**
094         * Set the ISO format used for this date.
095         * @param iso the {@link ISO} format
096         * @since 3.2
097         */
098        public void setIso(ISO iso) {
099                this.iso = iso;
100        }
101
102        /**
103         * Set the style to use to format date values.
104         * <p>If not specified, DateFormat's default style will be used.
105         * @see DateFormat#DEFAULT
106         * @see DateFormat#SHORT
107         * @see DateFormat#MEDIUM
108         * @see DateFormat#LONG
109         * @see DateFormat#FULL
110         */
111        public void setStyle(int style) {
112                this.style = style;
113        }
114
115        /**
116         * Set the two character to use to format date values. The first character used for
117         * the date style, the second is for the time style. Supported characters are
118         * <ul>
119         * <li>'S' = Small</li>
120         * <li>'M' = Medium</li>
121         * <li>'L' = Long</li>
122         * <li>'F' = Full</li>
123         * <li>'-' = Omitted</li>
124         * <ul>
125         * This method mimics the styles supported by Joda-Time.
126         * @param stylePattern two characters from the set {"S", "M", "L", "F", "-"}
127         * @since 3.2
128         */
129        public void setStylePattern(String stylePattern) {
130                this.stylePattern = stylePattern;
131        }
132
133        /**
134         * Set the TimeZone to normalize the date values into, if any.
135         */
136        public void setTimeZone(TimeZone timeZone) {
137                this.timeZone = timeZone;
138        }
139
140        /**
141         * Specify whether or not parsing is to be lenient. Default is false.
142         * <p>With lenient parsing, the parser may allow inputs that do not precisely match the format.
143         * With strict parsing, inputs must match the format exactly.
144         */
145        public void setLenient(boolean lenient) {
146                this.lenient = lenient;
147        }
148
149
150        @Override
151        public String print(Date date, Locale locale) {
152                return getDateFormat(locale).format(date);
153        }
154
155        @Override
156        public Date parse(String text, Locale locale) throws ParseException {
157                return getDateFormat(locale).parse(text);
158        }
159
160
161        protected DateFormat getDateFormat(Locale locale) {
162                DateFormat dateFormat = createDateFormat(locale);
163                if (this.timeZone != null) {
164                        dateFormat.setTimeZone(this.timeZone);
165                }
166                dateFormat.setLenient(this.lenient);
167                return dateFormat;
168        }
169
170        private DateFormat createDateFormat(Locale locale) {
171                if (StringUtils.hasLength(this.pattern)) {
172                        return new SimpleDateFormat(this.pattern, locale);
173                }
174                if (this.iso != null && this.iso != ISO.NONE) {
175                        String pattern = ISO_PATTERNS.get(this.iso);
176                        if (pattern == null) {
177                                throw new IllegalStateException("Unsupported ISO format " + this.iso);
178                        }
179                        SimpleDateFormat format = new SimpleDateFormat(pattern);
180                        format.setTimeZone(UTC);
181                        return format;
182                }
183                if (StringUtils.hasLength(this.stylePattern)) {
184                        int dateStyle = getStylePatternForChar(0);
185                        int timeStyle = getStylePatternForChar(1);
186                        if (dateStyle != -1 && timeStyle != -1) {
187                                return DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
188                        }
189                        if (dateStyle != -1) {
190                                return DateFormat.getDateInstance(dateStyle, locale);
191                        }
192                        if (timeStyle != -1) {
193                                return DateFormat.getTimeInstance(timeStyle, locale);
194                        }
195                        throw new IllegalStateException("Unsupported style pattern '" + this.stylePattern + "'");
196
197                }
198                return DateFormat.getDateInstance(this.style, locale);
199        }
200
201        private int getStylePatternForChar(int index) {
202                if (this.stylePattern != null && this.stylePattern.length() > index) {
203                        switch (this.stylePattern.charAt(index)) {
204                                case 'S': return DateFormat.SHORT;
205                                case 'M': return DateFormat.MEDIUM;
206                                case 'L': return DateFormat.LONG;
207                                case 'F': return DateFormat.FULL;
208                                case '-': return -1;
209                        }
210                }
211                throw new IllegalStateException("Unsupported style pattern '" + this.stylePattern + "'");
212        }
213
214}