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.standard; 018 019import java.time.format.DateTimeFormatter; 020import java.time.format.FormatStyle; 021import java.time.format.ResolverStyle; 022import java.util.TimeZone; 023 024import org.springframework.format.annotation.DateTimeFormat.ISO; 025import org.springframework.lang.UsesJava8; 026import org.springframework.util.Assert; 027import org.springframework.util.StringUtils; 028 029/** 030 * Factory that creates a JSR-310 {@link java.time.format.DateTimeFormatter}. 031 * 032 * <p>Formatters will be created using the defined {@link #setPattern pattern}, 033 * {@link #setIso ISO}, and <code>xxxStyle</code> methods (considered in that order). 034 * 035 * @author Juergen Hoeller 036 * @author Phillip Webb 037 * @since 4.0 038 * @see #createDateTimeFormatter() 039 * @see #createDateTimeFormatter(DateTimeFormatter) 040 * @see #setPattern 041 * @see #setIso 042 * @see #setDateStyle 043 * @see #setTimeStyle 044 * @see #setDateTimeStyle 045 * @see DateTimeFormatterFactoryBean 046 */ 047@UsesJava8 048public class DateTimeFormatterFactory { 049 050 private String pattern; 051 052 private ISO iso; 053 054 private FormatStyle dateStyle; 055 056 private FormatStyle timeStyle; 057 058 private TimeZone timeZone; 059 060 061 /** 062 * Create a new {@code DateTimeFormatterFactory} instance. 063 */ 064 public DateTimeFormatterFactory() { 065 } 066 067 /** 068 * Create a new {@code DateTimeFormatterFactory} instance. 069 * @param pattern the pattern to use to format date values 070 */ 071 public DateTimeFormatterFactory(String pattern) { 072 this.pattern = pattern; 073 } 074 075 076 /** 077 * Set the pattern to use to format date values. 078 * @param pattern the format pattern 079 */ 080 public void setPattern(String pattern) { 081 this.pattern = pattern; 082 } 083 084 /** 085 * Set the ISO format used to format date values. 086 * @param iso the ISO format 087 */ 088 public void setIso(ISO iso) { 089 this.iso = iso; 090 } 091 092 /** 093 * Set the style to use for date types. 094 */ 095 public void setDateStyle(FormatStyle dateStyle) { 096 this.dateStyle = dateStyle; 097 } 098 099 /** 100 * Set the style to use for time types. 101 */ 102 public void setTimeStyle(FormatStyle timeStyle) { 103 this.timeStyle = timeStyle; 104 } 105 106 /** 107 * Set the style to use for date and time types. 108 */ 109 public void setDateTimeStyle(FormatStyle dateTimeStyle) { 110 this.dateStyle = dateTimeStyle; 111 this.timeStyle = dateTimeStyle; 112 } 113 114 /** 115 * Set the two characters to use to format date values, in Joda-Time style. 116 * <p>The first character is used for the date style; the second is for 117 * 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 * <p>This method mimics the styles supported by Joda-Time. Note that 126 * JSR-310 natively favors {@link java.time.format.FormatStyle} as used for 127 * {@link #setDateStyle}, {@link #setTimeStyle} and {@link #setDateTimeStyle}. 128 * @param style two characters from the set {"S", "M", "L", "F", "-"} 129 */ 130 public void setStylePattern(String style) { 131 Assert.isTrue(style != null && style.length() == 2, "Style pattern must consist of two characters"); 132 this.dateStyle = convertStyleCharacter(style.charAt(0)); 133 this.timeStyle = convertStyleCharacter(style.charAt(1)); 134 } 135 136 private FormatStyle convertStyleCharacter(char c) { 137 switch (c) { 138 case 'S': return FormatStyle.SHORT; 139 case 'M': return FormatStyle.MEDIUM; 140 case 'L': return FormatStyle.LONG; 141 case 'F': return FormatStyle.FULL; 142 case '-': return null; 143 default: throw new IllegalArgumentException("Invalid style character '" + c + "'"); 144 } 145 } 146 147 /** 148 * Set the {@code TimeZone} to normalize the date values into, if any. 149 * @param timeZone the time zone 150 */ 151 public void setTimeZone(TimeZone timeZone) { 152 this.timeZone = timeZone; 153 } 154 155 156 /** 157 * Create a new {@code DateTimeFormatter} using this factory. 158 * <p>If no specific pattern or style has been defined, 159 * {@link FormatStyle#MEDIUM medium date time format} will be used. 160 * @return a new date time formatter 161 * @see #createDateTimeFormatter(DateTimeFormatter) 162 */ 163 public DateTimeFormatter createDateTimeFormatter() { 164 return createDateTimeFormatter(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); 165 } 166 167 /** 168 * Create a new {@code DateTimeFormatter} using this factory. 169 * <p>If no specific pattern or style has been defined, 170 * the supplied {@code fallbackFormatter} will be used. 171 * @param fallbackFormatter the fall-back formatter to use when no specific 172 * factory properties have been set (can be {@code null}). 173 * @return a new date time formatter 174 */ 175 public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) { 176 DateTimeFormatter dateTimeFormatter = null; 177 if (StringUtils.hasLength(this.pattern)) { 178 // Using strict parsing to align with Joda-Time and standard DateFormat behavior: 179 // otherwise, an overflow like e.g. Feb 29 for a non-leap-year wouldn't get rejected. 180 // However, with strict parsing, a year digit needs to be specified as 'u'... 181 String patternToUse = this.pattern.replace("yy", "uu"); 182 dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT); 183 } 184 else if (this.iso != null && this.iso != ISO.NONE) { 185 switch (this.iso) { 186 case DATE: 187 dateTimeFormatter = DateTimeFormatter.ISO_DATE; 188 break; 189 case TIME: 190 dateTimeFormatter = DateTimeFormatter.ISO_TIME; 191 break; 192 case DATE_TIME: 193 dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME; 194 break; 195 case NONE: 196 /* no-op */ 197 break; 198 default: 199 throw new IllegalStateException("Unsupported ISO format: " + this.iso); 200 } 201 } 202 else if (this.dateStyle != null && this.timeStyle != null) { 203 dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(this.dateStyle, this.timeStyle); 204 } 205 else if (this.dateStyle != null) { 206 dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(this.dateStyle); 207 } 208 else if (this.timeStyle != null) { 209 dateTimeFormatter = DateTimeFormatter.ofLocalizedTime(this.timeStyle); 210 } 211 212 if (dateTimeFormatter != null && this.timeZone != null) { 213 dateTimeFormatter = dateTimeFormatter.withZone(this.timeZone.toZoneId()); 214 } 215 return (dateTimeFormatter != null ? dateTimeFormatter : fallbackFormatter); 216 } 217 218}