DateTimeConverter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.beanutils2.converters;

  18. import java.text.DateFormat;
  19. import java.text.ParsePosition;
  20. import java.text.SimpleDateFormat;
  21. import java.time.Instant;
  22. import java.time.LocalDate;
  23. import java.time.LocalDateTime;
  24. import java.time.OffsetDateTime;
  25. import java.time.ZoneId;
  26. import java.time.ZonedDateTime;
  27. import java.time.temporal.TemporalAccessor;
  28. import java.util.Calendar;
  29. import java.util.Date;
  30. import java.util.Locale;
  31. import java.util.TimeZone;

  32. import org.apache.commons.beanutils2.ConversionException;

  33. /**
  34.  * {@link org.apache.commons.beanutils2.Converter} implementation that handles conversion to and from <strong>date/time</strong> objects.
  35.  * <p>
  36.  * This implementation handles conversion for the following <em>date/time</em> types.
  37.  * <ul>
  38.  * <li>{@link java.util.Date}</li>
  39.  * <li>{@link java.util.Calendar}</li>
  40.  * <li>{@link java.time.LocalDate}</li>
  41.  * <li>{@link java.time.LocalDateTime}</li>
  42.  * <li>{@link java.time.OffsetDateTime}</li>
  43.  * <li>{@link java.time.ZonedDateTime}</li>
  44.  * <li>{@link java.sql.Date}</li>
  45.  * <li>{@link java.sql.Time}</li>
  46.  * <li>{@link java.sql.Timestamp}</li>
  47.  * </ul>
  48.  *
  49.  * <h2>String Conversions (to and from)</h2> This class provides a number of ways in which date/time conversions to/from Strings can be achieved:
  50.  * <ul>
  51.  * <li>Using the SHORT date format for the default Locale, configure using:
  52.  * <ul>
  53.  * <li>{@code setUseLocaleFormat(true)}</li>
  54.  * </ul>
  55.  * </li>
  56.  * <li>Using the SHORT date format for a specified Locale, configure using:
  57.  * <ul>
  58.  * <li>{@code setLocale(Locale)}</li>
  59.  * </ul>
  60.  * </li>
  61.  * <li>Using the specified date pattern(s) for the default Locale, configure using:
  62.  * <ul>
  63.  * <li>Either {@code setPattern(String)} or {@code setPatterns(String[])}</li>
  64.  * </ul>
  65.  * </li>
  66.  * <li>Using the specified date pattern(s) for a specified Locale, configure using:
  67.  * <ul>
  68.  * <li>{@code setPattern(String)} or {@code setPatterns(String[]) and...}</li>
  69.  * <li>{@code setLocale(Locale)}</li>
  70.  * </ul>
  71.  * </li>
  72.  * <li>If none of the above are configured the {@code toDate(String)} method is used to convert from String to Date and the Dates's {@code toString()} method
  73.  * used to convert from Date to String.</li>
  74.  * </ul>
  75.  *
  76.  * <p>
  77.  * The <strong>Time Zone</strong> to use with the date format can be specified using the {@link #setTimeZone(TimeZone)} method.
  78.  *
  79.  * @param <D> The default value type.
  80.  * @since 1.8.0
  81.  */
  82. public abstract class DateTimeConverter<D> extends AbstractConverter<D> {

  83.     private String[] patterns;
  84.     private String displayPatterns;
  85.     private Locale locale;
  86.     private TimeZone timeZone;
  87.     private boolean useLocaleFormat;

  88.     /**
  89.      * Constructs a Date/Time <em>Converter</em> that throws a {@code ConversionException} if an error occurs.
  90.      */
  91.     public DateTimeConverter() {
  92.     }

  93.     /**
  94.      * Constructs a Date/Time <em>Converter</em> that returns a default value if an error occurs.
  95.      *
  96.      * @param defaultValue The default value to be returned if the value to be converted is missing or an error occurs converting the value.
  97.      */
  98.     public DateTimeConverter(final D defaultValue) {
  99.         super(defaultValue);
  100.     }

  101.     /**
  102.      * Convert an input Date/Calendar object into a String.
  103.      * <p>
  104.      * <strong>N.B.</strong>If the converter has been configured to with one or more patterns (using {@code setPatterns()}), then the first pattern will be used
  105.      * to format the date into a String. Otherwise the default {@code DateFormat} for the default locale (and <em>style</em> if configured) will be used.
  106.      *
  107.      * @param value The input value to be converted
  108.      * @return the converted String value.
  109.      * @throws IllegalArgumentException if an error occurs converting to a String
  110.      */
  111.     @Override
  112.     protected String convertToString(final Object value) {
  113.         Date date = null;
  114.         if (value instanceof Date) {
  115.             date = (Date) value;
  116.         } else if (value instanceof Calendar) {
  117.             date = ((Calendar) value).getTime();
  118.         } else if (value instanceof Long) {
  119.             date = new Date(((Long) value).longValue());
  120.         } else if (value instanceof LocalDateTime) {
  121.             date = java.sql.Timestamp.valueOf((LocalDateTime) value);
  122.         } else if (value instanceof LocalDate) {
  123.             date = java.sql.Date.valueOf((LocalDate) value);
  124.         } else if (value instanceof ZonedDateTime) {
  125.             date = Date.from(((ZonedDateTime) value).toInstant());
  126.         } else if (value instanceof OffsetDateTime) {
  127.             date = Date.from(((OffsetDateTime) value).toInstant());
  128.         } else if (value instanceof TemporalAccessor) {
  129.             // Backstop for other TemporalAccessor implementations.
  130.             date = Date.from(Instant.from((TemporalAccessor) value));
  131.         }

  132.         String result = null;
  133.         if (useLocaleFormat && date != null) {
  134.             DateFormat format = null;
  135.             if (patterns != null && patterns.length > 0) {
  136.                 format = getFormat(patterns[0]);
  137.             } else {
  138.                 format = getFormat(locale, timeZone);
  139.             }
  140.             logFormat("Formatting", format);
  141.             result = format.format(date);
  142.             if (log().isDebugEnabled()) {
  143.                 log().debug("    Converted  to String using format '" + result + "'");
  144.             }
  145.         } else {
  146.             result = value.toString();
  147.             if (log().isDebugEnabled()) {
  148.                 log().debug("    Converted  to String using toString() '" + result + "'");
  149.             }
  150.         }
  151.         return result;
  152.     }

  153.     /**
  154.      * Convert the input object into a Date object of the specified type.
  155.      * <p>
  156.      * This method handles conversions between the following types:
  157.      * <ul>
  158.      * <li>{@link java.util.Date}</li>
  159.      * <li>{@link java.util.Calendar}</li>
  160.      * <li>{@link java.time.LocalDate}</li>
  161.      * <li>{@link java.time.LocalDateTime}</li>
  162.      * <li>{@link java.time.OffsetDateTime}</li>
  163.      * <li>{@link java.time.ZonedDateTime}</li>
  164.      * <li>{@link java.sql.Date}</li>
  165.      * <li>{@link java.sql.Time}</li>
  166.      * <li>{@link java.sql.Timestamp}</li>
  167.      * </ul>
  168.      *
  169.      * It also handles conversion from a {@code String} to any of the above types.
  170.      * <p>
  171.      *
  172.      * For {@code String} conversion, if the converter has been configured with one or more patterns (using {@code setPatterns()}), then the conversion is
  173.      * attempted with each of the specified patterns. Otherwise the default {@code DateFormat} for the default locale (and <em>style</em> if configured) will be
  174.      * used.
  175.      *
  176.      * @param <T>        The desired target type of the conversion.
  177.      * @param targetType Data type to which this value should be converted.
  178.      * @param value      The input value to be converted.
  179.      * @return The converted value.
  180.      * @throws Exception if conversion cannot be performed successfully
  181.      */
  182.     @Override
  183.     protected <T> T convertToType(final Class<T> targetType, final Object value) throws Exception {
  184.         final Class<?> sourceType = value.getClass();

  185.         // Handle java.sql.Timestamp
  186.         if (value instanceof java.sql.Timestamp) {

  187.             // N.B. Prior to JDK 1.4 the Timestamp's getTime() method
  188.             // didn't include the milliseconds. The following code
  189.             // ensures it works consistently across JDK versions
  190.             final java.sql.Timestamp timestamp = (java.sql.Timestamp) value;
  191.             long timeInMillis = timestamp.getTime() / 1000 * 1000;
  192.             timeInMillis += timestamp.getNanos() / 1000000;

  193.             return toDate(targetType, timeInMillis);
  194.         }

  195.         // Handle Date (includes java.sql.Date & java.sql.Time)
  196.         if (value instanceof Date) {
  197.             final Date date = (Date) value;
  198.             return toDate(targetType, date.getTime());
  199.         }

  200.         // Handle Calendar
  201.         if (value instanceof Calendar) {
  202.             final Calendar calendar = (Calendar) value;
  203.             return toDate(targetType, calendar.getTime().getTime());
  204.         }

  205.         // Handle Long
  206.         if (value instanceof Long) {
  207.             final Long longObj = (Long) value;
  208.             return toDate(targetType, longObj.longValue());
  209.         }

  210.         // Handle LocalDate
  211.         if (value instanceof LocalDate) {
  212.             final LocalDate date = (LocalDate) value;
  213.             return toDate(targetType, date.atStartOfDay(getZoneId()).toInstant().toEpochMilli());
  214.         }

  215.         // Handle LocalDateTime
  216.         if (value instanceof LocalDateTime) {
  217.             final LocalDateTime date = (LocalDateTime) value;
  218.             return toDate(targetType, date.atZone(getZoneId()).toInstant().toEpochMilli());
  219.         }

  220.         // Handle ZonedDateTime
  221.         if (value instanceof ZonedDateTime) {
  222.             final ZonedDateTime date = (ZonedDateTime) value;
  223.             return toDate(targetType, date.toInstant().toEpochMilli());
  224.         }

  225.         // Handle OffsetDateTime
  226.         if (value instanceof OffsetDateTime) {
  227.             final OffsetDateTime date = (OffsetDateTime) value;
  228.             return toDate(targetType, date.toInstant().toEpochMilli());
  229.         }

  230.         // Convert all other types to String & handle
  231.         final String stringValue = toTrim(value);
  232.         if (stringValue.isEmpty()) {
  233.             return handleMissing(targetType);
  234.         }

  235.         // Parse the Date/Time
  236.         if (useLocaleFormat) {
  237.             Calendar calendar = null;
  238.             if (patterns != null && patterns.length > 0) {
  239.                 calendar = parse(sourceType, targetType, stringValue);
  240.             } else {
  241.                 final DateFormat format = getFormat(locale, timeZone);
  242.                 calendar = parse(sourceType, targetType, stringValue, format);
  243.             }
  244.             if (Calendar.class.isAssignableFrom(targetType)) {
  245.                 return targetType.cast(calendar);
  246.             }
  247.             return toDate(targetType, calendar.getTime().getTime());
  248.         }

  249.         // Default String conversion
  250.         return toDate(targetType, stringValue);
  251.     }

  252.     /**
  253.      * Gets a {@code DateFormat} for the Locale.
  254.      *
  255.      * @param locale   The Locale to create the Format with (may be null)
  256.      * @param timeZone The Time Zone create the Format with (may be null)
  257.      * @return A Date Format.
  258.      */
  259.     protected DateFormat getFormat(final Locale locale, final TimeZone timeZone) {
  260.         DateFormat format = null;
  261.         if (locale == null) {
  262.             format = DateFormat.getDateInstance(DateFormat.SHORT);
  263.         } else {
  264.             format = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  265.         }
  266.         if (timeZone != null) {
  267.             format.setTimeZone(timeZone);
  268.         }
  269.         return format;
  270.     }

  271.     /**
  272.      * Create a date format for the specified pattern.
  273.      *
  274.      * @param pattern The date pattern
  275.      * @return The DateFormat
  276.      */
  277.     private DateFormat getFormat(final String pattern) {
  278.         final DateFormat format = new SimpleDateFormat(pattern);
  279.         if (timeZone != null) {
  280.             format.setTimeZone(timeZone);
  281.         }
  282.         return format;
  283.     }

  284.     /**
  285.      * Gets the Locale for the <em>Converter</em> (or {@code null} if none specified).
  286.      *
  287.      * @return The locale to use for conversion
  288.      */
  289.     public Locale getLocale() {
  290.         return locale;
  291.     }

  292.     /**
  293.      * Gets the date format patterns used to convert dates to/from a {@link String} (or {@code null} if none specified).
  294.      *
  295.      * @see SimpleDateFormat
  296.      * @return Array of format patterns.
  297.      */
  298.     public String[] getPatterns() {
  299.         return patterns.clone();
  300.     }

  301.     /**
  302.      * Gets the Time Zone to use when converting dates (or {@code null} if none specified.
  303.      *
  304.      * @return The Time Zone.
  305.      */
  306.     public TimeZone getTimeZone() {
  307.         return timeZone;
  308.     }

  309.     /**
  310.      * Gets the {@code java.time.ZoneId</code> from the <code>java.util.Timezone} set or use the system default if no time zone is set.
  311.      *
  312.      * @return the {@code ZoneId}
  313.      */
  314.     private ZoneId getZoneId() {
  315.         return timeZone == null ? ZoneId.systemDefault() : timeZone.toZoneId();
  316.     }

  317.     /**
  318.      * Log the {@code DateFormat} creation.
  319.      *
  320.      * @param action The action the format is being used for
  321.      * @param format The Date format
  322.      */
  323.     private void logFormat(final String action, final DateFormat format) {
  324.         if (log().isDebugEnabled()) {
  325.             final StringBuilder buffer = new StringBuilder(45);
  326.             buffer.append("    ");
  327.             buffer.append(action);
  328.             buffer.append(" with Format");
  329.             if (format instanceof SimpleDateFormat) {
  330.                 buffer.append("[");
  331.                 buffer.append(((SimpleDateFormat) format).toPattern());
  332.                 buffer.append("]");
  333.             }
  334.             buffer.append(" for ");
  335.             if (locale == null) {
  336.                 buffer.append("default locale");
  337.             } else {
  338.                 buffer.append("locale[");
  339.                 buffer.append(locale);
  340.                 buffer.append("]");
  341.             }
  342.             if (timeZone != null) {
  343.                 buffer.append(", TimeZone[");
  344.                 buffer.append(timeZone);
  345.                 buffer.append("]");
  346.             }
  347.             log().debug(buffer.toString());
  348.         }
  349.     }

  350.     /**
  351.      * Parse a String date value using the set of patterns.
  352.      *
  353.      * @param sourceType The type of the value being converted
  354.      * @param targetType The type to convert the value to.
  355.      * @param value      The String date value.
  356.      * @return The converted Date object.
  357.      * @throws Exception if an error occurs parsing the date.
  358.      */
  359.     private Calendar parse(final Class<?> sourceType, final Class<?> targetType, final String value) throws Exception {
  360.         Exception firstEx = null;
  361.         for (final String pattern : patterns) {
  362.             try {
  363.                 return parse(sourceType, targetType, value, getFormat(pattern));
  364.             } catch (final Exception ex) {
  365.                 if (firstEx == null) {
  366.                     firstEx = ex;
  367.                 }
  368.             }
  369.         }
  370.         if (patterns.length > 1) {
  371.             throw ConversionException.format("Error converting '%s' to '%s' using  patterns '%s'", toString(sourceType), toString(targetType), displayPatterns);
  372.         }
  373.         if (firstEx != null) {
  374.             throw firstEx;
  375.         }
  376.         return null;
  377.     }

  378.     /**
  379.      * Parse a String into a {@code Calendar} object using the specified {@code DateFormat}.
  380.      *
  381.      * @param sourceType The type of the value being converted
  382.      * @param targetType The type to convert the value to
  383.      * @param value      The String date value.
  384.      * @param format     The DateFormat to parse the String value.
  385.      * @return The converted Calendar object.
  386.      * @throws ConversionException if the String cannot be converted.
  387.      */
  388.     private Calendar parse(final Class<?> sourceType, final Class<?> targetType, final String value, final DateFormat format) {
  389.         logFormat("Parsing", format);
  390.         format.setLenient(false);
  391.         final ParsePosition pos = new ParsePosition(0);
  392.         final Date parsedDate = format.parse(value, pos); // ignore the result (use the Calendar)
  393.         final int errorIndex = pos.getErrorIndex();
  394.         if (errorIndex >= 0 || pos.getIndex() != value.length() || parsedDate == null) {
  395.             String msg = "Error converting '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
  396.             if (format instanceof SimpleDateFormat) {
  397.                 final SimpleDateFormat simpleFormat = (SimpleDateFormat) format;
  398.                 msg += String.format(" using pattern '%s', localized pattern '%s', errorIndex %,d, calendar type %s, this %s", simpleFormat.toPattern(),
  399.                         simpleFormat.toLocalizedPattern(), errorIndex, format.getCalendar().getClass().getSimpleName(), this);
  400.             }
  401.             if (log().isDebugEnabled()) {
  402.                 log().debug("    " + msg);
  403.             }
  404.             throw new ConversionException(msg);
  405.         }
  406.         return format.getCalendar();
  407.     }

  408.     /**
  409.      * Sets the Locale for the <em>Converter</em>.
  410.      *
  411.      * @param locale The Locale.
  412.      */
  413.     public void setLocale(final Locale locale) {
  414.         this.locale = locale;
  415.         setUseLocaleFormat(true);
  416.     }

  417.     /**
  418.      * Sets a date format pattern to use to convert dates to/from a {@link String}.
  419.      *
  420.      * @see SimpleDateFormat
  421.      * @param pattern The format pattern.
  422.      */
  423.     public void setPattern(final String pattern) {
  424.         setPatterns(new String[] { pattern });
  425.     }

  426.     /**
  427.      * Sets the date format patterns to use to convert dates to/from a {@link String}.
  428.      *
  429.      * @see SimpleDateFormat
  430.      * @param patterns Array of format patterns.
  431.      */
  432.     public void setPatterns(final String[] patterns) {
  433.         this.patterns = patterns != null ? patterns.clone() : null;
  434.         if (this.patterns != null && this.patterns.length > 1) {
  435.             displayPatterns = String.join(", ", this.patterns);
  436.         }
  437.         setUseLocaleFormat(true);
  438.     }

  439.     /**
  440.      * Sets the Time Zone to use when converting dates.
  441.      *
  442.      * @param timeZone The Time Zone.
  443.      */
  444.     public void setTimeZone(final TimeZone timeZone) {
  445.         this.timeZone = timeZone;
  446.     }

  447.     /**
  448.      * Indicate whether conversion should use a format/pattern or not.
  449.      *
  450.      * @param useLocaleFormat {@code true} if the format for the locale should be used, otherwise {@code false}
  451.      */
  452.     public void setUseLocaleFormat(final boolean useLocaleFormat) {
  453.         this.useLocaleFormat = useLocaleFormat;
  454.     }

  455.     /**
  456.      * Convert a long value to the specified Date type for this <em>Converter</em>.
  457.      * <p>
  458.      *
  459.      * This method handles conversion to the following types:
  460.      * <ul>
  461.      * <li>{@link java.util.Date}</li>
  462.      * <li>{@link java.util.Calendar}</li>
  463.      * <li>{@link java.time.LocalDate}</li>
  464.      * <li>{@link java.time.LocalDateTime}</li>
  465.      * <li>{@link java.time.ZonedDateTime}</li>
  466.      * <li>{@link java.sql.Date}</li>
  467.      * <li>{@link java.sql.Time}</li>
  468.      * <li>{@link java.sql.Timestamp}</li>
  469.      * </ul>
  470.      *
  471.      * @param <T>   The target type
  472.      * @param type  The Date type to convert to
  473.      * @param value The long value to convert.
  474.      * @return The converted date value.
  475.      */
  476.     private <T> T toDate(final Class<T> type, final long value) {
  477.         // java.util.Date
  478.         if (type.equals(Date.class)) {
  479.             return type.cast(new Date(value));
  480.         }

  481.         // java.sql.Date
  482.         if (type.equals(java.sql.Date.class)) {
  483.             return type.cast(new java.sql.Date(value));
  484.         }

  485.         // java.sql.Time
  486.         if (type.equals(java.sql.Time.class)) {
  487.             return type.cast(new java.sql.Time(value));
  488.         }

  489.         // java.sql.Timestamp
  490.         if (type.equals(java.sql.Timestamp.class)) {
  491.             return type.cast(new java.sql.Timestamp(value));
  492.         }

  493.         // java.time.LocalDateTime
  494.         if (type.equals(LocalDate.class)) {
  495.             final LocalDate localDate = Instant.ofEpochMilli(value).atZone(getZoneId()).toLocalDate();
  496.             return type.cast(localDate);
  497.         }

  498.         // java.time.LocalDateTime
  499.         if (type.equals(LocalDateTime.class)) {
  500.             final LocalDateTime localDateTime = Instant.ofEpochMilli(value).atZone(getZoneId()).toLocalDateTime();
  501.             return type.cast(localDateTime);
  502.         }

  503.         // java.time.ZonedDateTime
  504.         if (type.equals(ZonedDateTime.class)) {
  505.             final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(value), getZoneId());
  506.             return type.cast(zonedDateTime);
  507.         }

  508.         // java.time.OffsetDateTime
  509.         if (type.equals(OffsetDateTime.class)) {
  510.             final OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(Instant.ofEpochMilli(value), getZoneId());
  511.             return type.cast(offsetDateTime);
  512.         }

  513.         // java.util.Calendar
  514.         if (type.equals(Calendar.class)) {
  515.             Calendar calendar = null;
  516.             if (locale == null && timeZone == null) {
  517.                 calendar = Calendar.getInstance();
  518.             } else if (locale == null) {
  519.                 calendar = Calendar.getInstance(timeZone);
  520.             } else if (timeZone == null) {
  521.                 calendar = Calendar.getInstance(locale);
  522.             } else {
  523.                 calendar = Calendar.getInstance(timeZone, locale);
  524.             }
  525.             calendar.setTime(new Date(value));
  526.             calendar.setLenient(false);
  527.             return type.cast(calendar);
  528.         }

  529.         final String msg = toString(getClass()) + " cannot handle conversion to '" + toString(type) + "'";
  530.         if (log().isWarnEnabled()) {
  531.             log().warn("    " + msg);
  532.         }
  533.         throw new ConversionException(msg);
  534.     }

  535.     /**
  536.      * Default String to Date conversion.
  537.      * <p>
  538.      * This method handles conversion from a String to the following types:
  539.      * <ul>
  540.      * <li>{@link java.sql.Date}</li>
  541.      * <li>{@link java.sql.Time}</li>
  542.      * <li>{@link java.sql.Timestamp}</li>
  543.      * </ul>
  544.      * <p>
  545.      * <strong>N.B.</strong> No default String conversion mechanism is provided for {@link java.util.Date} and {@link java.util.Calendar} type.
  546.      *
  547.      * @param <T>   The target type
  548.      * @param type  The date type to convert to
  549.      * @param value The String value to convert.
  550.      * @return The converted Number value.
  551.      */
  552.     private <T> T toDate(final Class<T> type, final String value) {
  553.         // java.sql.Date
  554.         if (type.equals(java.sql.Date.class)) {
  555.             try {
  556.                 return type.cast(java.sql.Date.valueOf(value));
  557.             } catch (final IllegalArgumentException e) {
  558.                 throw new ConversionException("String must be in JDBC format [yyyy-MM-dd] to create a java.sql.Date");
  559.             }
  560.         }

  561.         // java.sql.Time
  562.         if (type.equals(java.sql.Time.class)) {
  563.             try {
  564.                 return type.cast(java.sql.Time.valueOf(value));
  565.             } catch (final IllegalArgumentException e) {
  566.                 throw new ConversionException("String must be in JDBC format [HH:mm:ss] to create a java.sql.Time");
  567.             }
  568.         }

  569.         // java.sql.Timestamp
  570.         if (type.equals(java.sql.Timestamp.class)) {
  571.             try {
  572.                 return type.cast(java.sql.Timestamp.valueOf(value));
  573.             } catch (final IllegalArgumentException e) {
  574.                 throw new ConversionException("String must be in JDBC format [yyyy-MM-dd HH:mm:ss.fffffffff] " + "to create a java.sql.Timestamp");
  575.             }
  576.         }

  577.         final String msg = toString(getClass()) + " does not support default String to '" + toString(type) + "' conversion.";
  578.         if (log().isWarnEnabled()) {
  579.             log().warn("    " + msg);
  580.             log().warn("    (N.B. Re-configure Converter or use alternative implementation)");
  581.         }
  582.         throw new ConversionException(msg);
  583.     }

  584.     /**
  585.      * Provide a String representation of this date/time converter.
  586.      *
  587.      * @return A String representation of this date/time converter
  588.      */
  589.     @Override
  590.     public String toString() {
  591.         final StringBuilder buffer = new StringBuilder();
  592.         buffer.append(toString(getClass()));
  593.         buffer.append("[UseDefault=");
  594.         buffer.append(isUseDefault());
  595.         buffer.append(", UseLocaleFormat=");
  596.         buffer.append(useLocaleFormat);
  597.         if (displayPatterns != null) {
  598.             buffer.append(", Patterns={");
  599.             buffer.append(displayPatterns);
  600.             buffer.append('}');
  601.         }
  602.         if (locale != null) {
  603.             buffer.append(", Locale=");
  604.             buffer.append(locale);
  605.         }
  606.         if (timeZone != null) {
  607.             buffer.append(", TimeZone=");
  608.             buffer.append(timeZone);
  609.         }
  610.         buffer.append(']');
  611.         return buffer.toString();
  612.     }
  613. }