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