View Javadoc
1   /*
2    * Copyright 2002-2013 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;
18  
19  import java.text.DateFormat;
20  import java.text.ParseException;
21  import java.text.SimpleDateFormat;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Locale;
26  import java.util.Map;
27  import java.util.TimeZone;
28  
29  import org.springframework.format.Formatter;
30  import org.springframework.format.annotation.DateTimeFormat;
31  import org.springframework.format.annotation.DateTimeFormat.ISO;
32  import org.springframework.util.StringUtils;
33  
34  /**
35   * A formatter for {@link java.util.Date} types.
36   * Allows the configuration of an explicit date pattern and locale.
37   *
38   * @author Keith Donald
39   * @author Juergen Hoeller
40   * @author Phillip Webb
41   * @since 3.0
42   * @see SimpleDateFormat
43   */
44  public class DateFormatter implements Formatter<Date> {
45  
46  	private static final Map<ISO, String> ISO_PATTERNS;
47  
48  	static {
49  		Map<ISO, String> formats = new HashMap<DateTimeFormat.ISO, String>(4);
50  		formats.put(ISO.DATE, "yyyy-MM-dd");
51  		formats.put(ISO.TIME, "HH:mm:ss.SSSZ");
52  		formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
53  		ISO_PATTERNS = Collections.unmodifiableMap(formats);
54  	}
55  
56  
57  	private String pattern;
58  
59  	private int style = DateFormat.DEFAULT;
60  
61  	private String stylePattern;
62  
63  	private ISO iso;
64  
65  	private TimeZone timeZone;
66  
67  	private boolean lenient = false;
68  
69  
70  	/**
71  	 * Create a new default DateFormatter.
72  	 */
73  	public DateFormatter() {
74  	}
75  
76  	/**
77  	 * Create a new DateFormatter for the given date pattern.
78  	 */
79  	public DateFormatter(String pattern) {
80  		this.pattern = pattern;
81  	}
82  
83  
84  	/**
85  	 * Set the pattern to use to format date values.
86  	 * <p>If not specified, DateFormat's default style will be used.
87  	 */
88  	public void setPattern(String pattern) {
89  		this.pattern = pattern;
90  	}
91  
92  	/**
93  	 * Set the ISO format used for this date.
94  	 * @param iso the {@link ISO} format
95  	 * @since 3.2
96  	 */
97  	public void setIso(ISO iso) {
98  		this.iso = iso;
99  	}
100 
101 	/**
102 	 * Set the style to use to format date values.
103 	 * <p>If not specified, DateFormat's default style will be used.
104 	 * @see DateFormat#DEFAULT
105 	 * @see DateFormat#SHORT
106 	 * @see DateFormat#MEDIUM
107 	 * @see DateFormat#LONG
108 	 * @see DateFormat#FULL
109 	 */
110 	public void setStyle(int style) {
111 		this.style = style;
112 	}
113 
114 	/**
115 	 * Set the two character to use to format date values. The first character used for
116 	 * the date style, the second is for the time style. Supported characters are
117 	 * <ul>
118 	 * <li>'S' = Small</li>
119 	 * <li>'M' = Medium</li>
120 	 * <li>'L' = Long</li>
121 	 * <li>'F' = Full</li>
122 	 * <li>'-' = Omitted</li>
123 	 * <ul>
124 	 * This method mimics the styles supported by Joda-Time.
125 	 * @param stylePattern two characters from the set {"S", "M", "L", "F", "-"}
126 	 * @since 3.2
127 	 */
128 	public void setStylePattern(String stylePattern) {
129 		this.stylePattern = stylePattern;
130 	}
131 
132 	/**
133 	 * Set the TimeZone to normalize the date values into, if any.
134 	 */
135 	public void setTimeZone(TimeZone timeZone) {
136 		this.timeZone = timeZone;
137 	}
138 
139 	/**
140 	 * Specify whether or not parsing is to be lenient. Default is false.
141 	 * <p>With lenient parsing, the parser may allow inputs that do not precisely match the format.
142 	 * With strict parsing, inputs must match the format exactly.
143 	 */
144 	public void setLenient(boolean lenient) {
145 		this.lenient = lenient;
146 	}
147 
148 
149 	@Override
150 	public String print(Date date, Locale locale) {
151 		return getDateFormat(locale).format(date);
152 	}
153 
154 	@Override
155 	public Date parse(String text, Locale locale) throws ParseException {
156 		return getDateFormat(locale).parse(text);
157 	}
158 
159 
160 	protected DateFormat getDateFormat(Locale locale) {
161 		DateFormat dateFormat = createDateFormat(locale);
162 		if (this.timeZone != null) {
163 			dateFormat.setTimeZone(this.timeZone);
164 		}
165 		dateFormat.setLenient(this.lenient);
166 		return dateFormat;
167 	}
168 
169 	private DateFormat createDateFormat(Locale locale) {
170 		if (StringUtils.hasLength(this.pattern)) {
171 			return new SimpleDateFormat(this.pattern, locale);
172 		}
173 		if (this.iso != null && this.iso != ISO.NONE) {
174 			String pattern = ISO_PATTERNS.get(this.iso);
175 			if (pattern == null) {
176 				throw new IllegalStateException("Unsupported ISO format " + this.iso);
177 			}
178 			SimpleDateFormat format = new SimpleDateFormat(pattern);
179 			format.setTimeZone(TimeZone.getTimeZone("UTC"));
180 			return format;
181 		}
182 		if (StringUtils.hasLength(this.stylePattern)) {
183 			int dateStyle = getStylePatternForChar(0);
184 			int timeStyle = getStylePatternForChar(1);
185 			if (dateStyle != -1 && timeStyle != -1) {
186 				return DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
187 			}
188 			if (dateStyle != -1) {
189 				return DateFormat.getDateInstance(dateStyle, locale);
190 			}
191 			if (timeStyle != -1) {
192 				return DateFormat.getTimeInstance(timeStyle, locale);
193 			}
194 			throw new IllegalStateException("Unsupported style pattern '"+ this.stylePattern+ "'");
195 
196 		}
197 		return DateFormat.getDateInstance(this.style, locale);
198 	}
199 
200 	private int getStylePatternForChar(int index) {
201 		if (this.stylePattern != null && this.stylePattern.length() > index) {
202 			switch (this.stylePattern.charAt(index)) {
203 				case 'S': return DateFormat.SHORT;
204 				case 'M': return DateFormat.MEDIUM;
205 				case 'L': return DateFormat.LONG;
206 				case 'F': return DateFormat.FULL;
207 				case '-': return -1;
208 			}
209 		}
210 		throw new IllegalStateException("Unsupported style pattern '" + this.stylePattern + "'");
211 	}
212 
213 }