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}