DateLocaleConverter.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.beanutils2.locale.converters;
- import java.text.DateFormat;
- import java.text.DateFormatSymbols;
- import java.text.ParseException;
- import java.text.ParsePosition;
- import java.text.SimpleDateFormat;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.Locale;
- import org.apache.commons.beanutils2.ConversionException;
- import org.apache.commons.beanutils2.locale.BaseLocaleConverter;
- import org.apache.commons.beanutils2.locale.LocaleConverter;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- /**
- * Standard {@link org.apache.commons.beanutils2.locale.LocaleConverter} implementation that converts an incoming locale-sensitive String into a
- * {@link java.util.Date} object, optionally using a default value or throwing a {@link org.apache.commons.beanutils2.ConversionException} if a conversion error
- * occurs.
- *
- * @param <D> The Date type.
- */
- public class DateLocaleConverter<D extends Date> extends BaseLocaleConverter<D> {
- /**
- * Builds instances of {@link DateLocaleConverter}.
- *
- * @param <B> The builder type.
- * @param <D> The Date type.
- */
- public static class Builder<B extends Builder<B, D>, D extends Date> extends BaseLocaleConverter.Builder<B, D> {
- /** Should the date conversion be lenient? */
- private boolean lenient;
- /**
- * Constructs a new instance.
- */
- public Builder() {
- // empty
- }
- /**
- * Gets a new instance.
- * <p>
- * Defaults construct a {@link LocaleConverter} that will throw a {@link ConversionException} if a conversion error occurs. The locale is the default
- * locale for this instance of the Java Virtual Machine and an unlocalized pattern is used for the conversion.
- * </p>
- *
- * @return a new instance.
- */
- @Override
- public DateLocaleConverter<D> get() {
- return new DateLocaleConverter<>(defaultValue, locale, pattern, useDefault || defaultValue != null, localizedPattern, lenient);
- }
- /**
- * Tests whether date formatting is lenient.
- *
- * @return true if the {@code DateFormat} used for formatting is lenient
- * @see java.text.DateFormat#isLenient()
- */
- public boolean isLenient() {
- return lenient;
- }
- /**
- * Sets the leniency policy.
- *
- * @param lenient the leniency policy.
- * @return {@code this} instance.
- */
- public B setLenient(final boolean lenient) {
- this.lenient = lenient;
- return asThis();
- }
- }
- /**
- * Default Pattern Characters
- */
- private static final String DEFAULT_PATTERN_CHARS = DateLocaleConverter.initDefaultChars();
- /** All logging goes through this logger */
- private static final Log LOG = LogFactory.getLog(DateLocaleConverter.class);
- /**
- * Constructs a new builder.
- *
- * @param <B> The builder type.
- * @param <D> The Date type.
- * @return a new builder.
- */
- @SuppressWarnings("unchecked")
- public static <B extends Builder<B, D>, D extends Date> B builder() {
- return (B) new Builder<>();
- }
- /**
- * This method is called at class initialization time to define the value for constant member DEFAULT_PATTERN_CHARS. All other methods needing this data
- * should just read that constant.
- */
- private static String initDefaultChars() {
- return new DateFormatSymbols(Locale.US).getLocalPatternChars();
- }
- /** Should the date conversion be lenient? */
- private final boolean isLenient;
- /**
- * Constructs a new instance.
- *
- * @param defaultValue default value.
- * @param locale locale.
- * @param pattern pattern.
- * @param useDefault use the default.
- * @param locPattern localized pattern.
- * @param lenient leniency policy.
- */
- protected DateLocaleConverter(final D defaultValue, final Locale locale, final String pattern, final boolean useDefault, final boolean locPattern,
- final boolean lenient) {
- super(defaultValue, locale, pattern, useDefault, locPattern);
- this.isLenient = lenient;
- }
- /**
- * Converts a pattern from a localized format to the default format.
- *
- * @param locale The locale
- * @param localizedPattern The pattern in 'local' symbol format
- * @return pattern in 'default' symbol format
- */
- private String convertLocalizedPattern(final String localizedPattern, final Locale locale) {
- if (localizedPattern == null) {
- return null;
- }
- // Note that this is a little obtuse.
- // However, it is the best way that anyone can come up with
- // that works with some 1.4 series JVM.
- // Get the symbols for the localized pattern
- final DateFormatSymbols localizedSymbols = new DateFormatSymbols(locale);
- final String localChars = localizedSymbols.getLocalPatternChars();
- if (DEFAULT_PATTERN_CHARS.equals(localChars)) {
- return localizedPattern;
- }
- // Convert the localized pattern to default
- String convertedPattern = null;
- try {
- convertedPattern = convertPattern(localizedPattern, localChars, DEFAULT_PATTERN_CHARS);
- } catch (final Exception ex) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Converting pattern '" + localizedPattern + "' for " + locale, ex);
- }
- }
- return convertedPattern;
- }
- /**
- * Converts a Pattern from one character set to another.
- */
- private String convertPattern(final String pattern, final String fromChars, final String toChars) {
- final StringBuilder converted = new StringBuilder();
- boolean quoted = false;
- for (int i = 0; i < pattern.length(); ++i) {
- char thisChar = pattern.charAt(i);
- if (quoted) {
- if (thisChar == '\'') {
- quoted = false;
- }
- } else if (thisChar == '\'') {
- quoted = true;
- } else if (thisChar >= 'a' && thisChar <= 'z' || thisChar >= 'A' && thisChar <= 'Z') {
- final int index = fromChars.indexOf(thisChar);
- if (index == -1) {
- throw new IllegalArgumentException("Illegal pattern character '" + thisChar + "'");
- }
- thisChar = toChars.charAt(index);
- }
- converted.append(thisChar);
- }
- if (quoted) {
- throw new IllegalArgumentException("Unfinished quote in pattern");
- }
- return converted.toString();
- }
- /**
- * Tests whether date formatting is lenient.
- *
- * @return true if the {@code DateFormat} used for formatting is lenient
- * @see java.text.DateFormat#isLenient()
- */
- public boolean isLenient() {
- return isLenient;
- }
- /**
- * Convert the specified locale-sensitive input object into an output object of the specified type.
- *
- * @param value The input object to be converted
- * @param pattern The pattern is used for the conversion
- * @return the converted Date value
- * @throws ConversionException if conversion cannot be performed successfully
- * @throws ParseException if an error occurs parsing
- */
- @Override
- protected D parse(final Object value, String pattern) throws ParseException {
- // Handle Date
- if (value instanceof Date) {
- return (D) value;
- }
- // Handle Calendar
- if (value instanceof Calendar) {
- return (D) ((Calendar) value).getTime();
- }
- if (localizedPattern) {
- pattern = convertLocalizedPattern(pattern, locale);
- }
- // Create Formatter - use default if pattern is null
- final DateFormat formatter = pattern == null ? DateFormat.getDateInstance(DateFormat.SHORT, locale) : new SimpleDateFormat(pattern, locale);
- formatter.setLenient(isLenient);
- // Parse the Date
- final ParsePosition pos = new ParsePosition(0);
- final String strValue = value.toString();
- final Object parsedValue = formatter.parseObject(strValue, pos);
- if (pos.getErrorIndex() > -1) {
- throw ConversionException.format("Error parsing date '%s' at position = %s", value, pos.getErrorIndex());
- }
- if (pos.getIndex() < strValue.length()) {
- throw ConversionException.format("Date '%s' contains unparsed characters from position = %s", value, pos.getIndex());
- }
- return (D) parsedValue;
- }
- }