001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.beanutils.locale.converters; 019 020import java.text.DateFormat; 021import java.text.DateFormatSymbols; 022import java.text.ParseException; 023import java.text.ParsePosition; 024import java.text.SimpleDateFormat; 025import java.util.Calendar; 026import java.util.Date; 027import java.util.Locale; 028 029import org.apache.commons.beanutils.ConversionException; 030import org.apache.commons.beanutils.locale.BaseLocaleConverter; 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033 034/** 035 * <p>Standard {@link org.apache.commons.beanutils.locale.LocaleConverter} 036 * implementation that converts an incoming 037 * locale-sensitive String into a <code>java.util.Date</code> object, 038 * optionally using a default value or throwing a 039 * {@link org.apache.commons.beanutils.ConversionException} 040 * if a conversion error occurs.</p> 041 * 042 */ 043 044public class DateLocaleConverter extends BaseLocaleConverter { 045 046 /** 047 * Default Pattern Characters 048 * 049 */ 050 private static final String DEFAULT_PATTERN_CHARS = DateLocaleConverter.initDefaultChars(); 051 052 /** 053 * This method is called at class initialization time to define the 054 * value for constant member DEFAULT_PATTERN_CHARS. All other methods needing 055 * this data should just read that constant. 056 */ 057 private static String initDefaultChars() { 058 final DateFormatSymbols defaultSymbols = new DateFormatSymbols(Locale.US); 059 return defaultSymbols.getLocalPatternChars(); 060 } 061 062 /** All logging goes through this logger */ 063 private final Log log = LogFactory.getLog(DateLocaleConverter.class); 064 065 /** Should the date conversion be lenient? */ 066 boolean isLenient; 067 068 /** 069 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 070 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 071 * if a conversion error occurs. The locale is the default locale for 072 * this instance of the Java Virtual Machine and an unlocalized pattern is used 073 * for the convertion. 074 * 075 */ 076 public DateLocaleConverter() { 077 078 this(false); 079 } 080 081 /** 082 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 083 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 084 * if a conversion error occurs. The locale is the default locale for 085 * this instance of the Java Virtual Machine. 086 * 087 * @param locPattern Indicate whether the pattern is localized or not 088 */ 089 public DateLocaleConverter(final boolean locPattern) { 090 091 this(Locale.getDefault(), locPattern); 092 } 093 094 /** 095 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 096 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 097 * if a conversion error occurs. An unlocalized pattern is used for the convertion. 098 * 099 * @param locale The locale 100 */ 101 public DateLocaleConverter(final Locale locale) { 102 103 this(locale, false); 104 } 105 106 /** 107 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 108 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 109 * if a conversion error occurs. 110 * 111 * @param locale The locale 112 * @param locPattern Indicate whether the pattern is localized or not 113 */ 114 public DateLocaleConverter(final Locale locale, final boolean locPattern) { 115 116 this(locale, (String) null, locPattern); 117 } 118 119 /** 120 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 121 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 122 * if a conversion error occurs. An unlocalized pattern is used for the convertion. 123 * 124 * @param locale The locale 125 * @param pattern The convertion pattern 126 */ 127 public DateLocaleConverter(final Locale locale, final String pattern) { 128 129 this(locale, pattern, false); 130 } 131 132 /** 133 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 134 * that will throw a {@link org.apache.commons.beanutils.ConversionException} 135 * if a conversion error occurs. 136 * 137 * @param locale The locale 138 * @param pattern The convertion pattern 139 * @param locPattern Indicate whether the pattern is localized or not 140 */ 141 public DateLocaleConverter(final Locale locale, final String pattern, final boolean locPattern) { 142 143 super(locale, pattern, locPattern); 144 } 145 146 /** 147 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 148 * that will return the specified default value 149 * if a conversion error occurs. The locale is the default locale for 150 * this instance of the Java Virtual Machine and an unlocalized pattern is used 151 * for the convertion. 152 * 153 * @param defaultValue The default value to be returned 154 */ 155 public DateLocaleConverter(final Object defaultValue) { 156 157 this(defaultValue, false); 158 } 159 160 /** 161 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 162 * that will return the specified default value 163 * if a conversion error occurs. The locale is the default locale for 164 * this instance of the Java Virtual Machine. 165 * 166 * @param defaultValue The default value to be returned 167 * @param locPattern Indicate whether the pattern is localized or not 168 */ 169 public DateLocaleConverter(final Object defaultValue, final boolean locPattern) { 170 171 this(defaultValue, Locale.getDefault(), locPattern); 172 } 173 174 /** 175 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 176 * that will return the specified default value 177 * if a conversion error occurs. An unlocalized pattern is used for the convertion. 178 * 179 * @param defaultValue The default value to be returned 180 * @param locale The locale 181 */ 182 public DateLocaleConverter(final Object defaultValue, final Locale locale) { 183 184 this(defaultValue, locale, false); 185 } 186 187 /** 188 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 189 * that will return the specified default value 190 * if a conversion error occurs. 191 * 192 * @param defaultValue The default value to be returned 193 * @param locale The locale 194 * @param locPattern Indicate whether the pattern is localized or not 195 */ 196 public DateLocaleConverter(final Object defaultValue, final Locale locale, final boolean locPattern) { 197 198 this(defaultValue, locale, null, locPattern); 199 } 200 201 /** 202 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 203 * that will return the specified default value 204 * if a conversion error occurs. An unlocalized pattern is used for the convertion. 205 * 206 * @param defaultValue The default value to be returned 207 * @param locale The locale 208 * @param pattern The convertion pattern 209 */ 210 public DateLocaleConverter(final Object defaultValue, final Locale locale, final String pattern) { 211 212 this(defaultValue, locale, pattern, false); 213 } 214 215 /** 216 * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter} 217 * that will return the specified default value 218 * if a conversion error occurs. 219 * 220 * @param defaultValue The default value to be returned 221 * @param locale The locale 222 * @param pattern The convertion pattern 223 * @param locPattern Indicate whether the pattern is localized or not 224 */ 225 public DateLocaleConverter(final Object defaultValue, final Locale locale, final String pattern, final boolean locPattern) { 226 227 super(defaultValue, locale, pattern, locPattern); 228 } 229 230 /** 231 * Convert a pattern from a localized format to the default format. 232 * 233 * @param locale The locale 234 * @param localizedPattern The pattern in 'local' symbol format 235 * @return pattern in 'default' symbol format 236 */ 237 private String convertLocalizedPattern(final String localizedPattern, final Locale locale) { 238 239 if (localizedPattern == null) { 240 return null; 241 } 242 243 // Note that this is a little obtuse. 244 // However, it is the best way that anyone can come up with 245 // that works with some 1.4 series JVM. 246 247 // Get the symbols for the localized pattern 248 final DateFormatSymbols localizedSymbols = new DateFormatSymbols(locale); 249 final String localChars = localizedSymbols.getLocalPatternChars(); 250 251 if (DEFAULT_PATTERN_CHARS.equals(localChars)) { 252 return localizedPattern; 253 } 254 255 // Convert the localized pattern to default 256 String convertedPattern = null; 257 try { 258 convertedPattern = convertPattern(localizedPattern, 259 localChars, 260 DEFAULT_PATTERN_CHARS); 261 } catch (final Exception ex) { 262 log.debug("Converting pattern '" + localizedPattern + "' for " + locale, ex); 263 } 264 return convertedPattern; 265 } 266 267 /** 268 * <p>Converts a Pattern from one character set to another.</p> 269 */ 270 private String convertPattern(final String pattern, final String fromChars, final String toChars) { 271 272 final StringBuilder converted = new StringBuilder(); 273 boolean quoted = false; 274 275 for (int i = 0; i < pattern.length(); ++i) { 276 char thisChar = pattern.charAt(i); 277 if (quoted) { 278 if (thisChar == '\'') { 279 quoted = false; 280 } 281 } else if (thisChar == '\'') { 282 quoted = true; 283 } else if (thisChar >= 'a' && thisChar <= 'z' || 284 thisChar >= 'A' && thisChar <= 'Z') { 285 final int index = fromChars.indexOf(thisChar); 286 if (index == -1) { 287 throw new IllegalArgumentException( 288 "Illegal pattern character '" + thisChar + "'"); 289 } 290 thisChar = toChars.charAt(index); 291 } 292 converted.append(thisChar); 293 } 294 295 if (quoted) { 296 throw new IllegalArgumentException("Unfinished quote in pattern"); 297 } 298 299 return converted.toString(); 300 } 301 302 /** 303 * Returns whether date formatting is lenient. 304 * 305 * @return true if the <code>DateFormat</code> used for formatting is lenient 306 * @see java.text.DateFormat#isLenient 307 */ 308 public boolean isLenient() { 309 return isLenient; 310 } 311 312 /** 313 * Convert the specified locale-sensitive input object into an output object of the 314 * specified type. 315 * 316 * @param value The input object to be converted 317 * @param pattern The pattern is used for the convertion 318 * @return the converted Date value 319 * @throws org.apache.commons.beanutils.ConversionException 320 * if conversion cannot be performed successfully 321 * @throws ParseException if an error occurs parsing 322 */ 323 @Override 324 protected Object parse(final Object value, String pattern) throws ParseException { 325 326 // Handle Date 327 if (value instanceof Date) { 328 return value; 329 } 330 331 // Handle Calendar 332 if (value instanceof Calendar) { 333 return ((Calendar) value).getTime(); 334 } 335 336 if (locPattern) { 337 pattern = convertLocalizedPattern(pattern, locale); 338 } 339 340 // Create Formatter - use default if pattern is null 341 final DateFormat formatter = pattern == null ? DateFormat.getDateInstance(DateFormat.SHORT, locale) 342 : new SimpleDateFormat(pattern, locale); 343 formatter.setLenient(isLenient); 344 345 // Parse the Date 346 final ParsePosition pos = new ParsePosition(0); 347 final String strValue = value.toString(); 348 final Object parsedValue = formatter.parseObject(strValue, pos); 349 if (pos.getErrorIndex() > -1) { 350 throw new ConversionException("Error parsing date '" + value + 351 "' at position="+ pos.getErrorIndex()); 352 } 353 if (pos.getIndex() < strValue.length()) { 354 throw new ConversionException("Date '" + value + 355 "' contains unparsed characters from position=" + pos.getIndex()); 356 } 357 358 return parsedValue; 359 } 360 361 /** 362 * Specify whether or not date-time parsing should be lenient. 363 * 364 * @param lenient true if the <code>DateFormat</code> used for formatting should be lenient 365 * @see java.text.DateFormat#setLenient 366 */ 367 public void setLenient(final boolean lenient) { 368 isLenient = lenient; 369 } 370 371}