View Javadoc

    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.beanutils.converters;
    18
    19  import java.util.Date;
    20  import java.util.Locale;
    21  import java.util.Calendar;
    22  import java.util.TimeZone;
    23  import java.text.DateFormat;
    24  import java.text.SimpleDateFormat;
    25  import java.text.ParsePosition;
    26  import org.apache.commons.beanutils.ConversionException;
    27
    28  /***
    29   * {@link org.apache.commons.beanutils.Converter} implementaion
    30   * that handles conversion to and from <b>date/time</b> objects.
    31   * <p>
    32   * This implementation handles conversion for the following
    33   * <i>date/time</i> types.
    34   * <ul>
    35   *     <li><code>java.util.Date</code></li>
    36   *     <li><code>java.util.Calendar</code></li>
    37   *     <li><code>java.sql.Date</code></li>
    38   *     <li><code>java.sql.Time</code></li>
    39   *     <li><code>java.sql.Timestamp</code></li>
    40   * </ul>
    41   *
    42   * <h3>String Conversions (to and from)</h3>
    43   * This class provides a number of ways in which date/time
    44   * conversions to/from Strings can be achieved:
    45   * <ul>
    46   *    <li>Using the SHORT date format for the default Locale, configure using:</li>
    47   *        <ul>
    48   *           <li><code>setUseLocaleFormat(true)</code></li>
    49   *        </ul>
    50   *    <li>Using the SHORT date format for a specified Locale, configure using:</li>
    51   *        <ul>
    52   *           <li><code>setLocale(Locale)</code></li>
    53   *        </ul>
    54   *    <li>Using the specified date pattern(s) for the default Locale, configure using:</li>
    55   *        <ul>
    56   *           <li>Either <code>setPattern(String)</code> or
    57   *                      <code>setPatterns(String[])</code></li>
    58   *        </ul>
    59   *    <li>Using the specified date pattern(s) for a specified Locale, configure using:</li>
    60   *        <ul>
    61   *           <li><code>setPattern(String)</code> or
    62   *                    <code>setPatterns(String[]) and...</code></li>
    63   *           <li><code>setLocale(Locale)</code></li>
    64   *        </ul>
    65   *    <li>If none of the above are configured the
    66   *        <code>toDate(String)</code> method is used to convert
    67   *        from String to Date and the Dates's
    68   *        <code>toString()</code> method used to convert from
    69   *        Date to String.</li>
    70   * </ul>
    71   *
    72   * <p>
    73   * The <b>Time Zone</b> to use with the date format can be specified
    74   * using the <code>setTimeZone()</code> method.
    75   *
    76   * @version $Revision: 555845 $ $Date: 2007-07-13 03:52:05 +0100 (Fri, 13 Jul 2007) $
    77   * @since 1.8.0
    78   */
    79  public class DateTimeConverter extends AbstractConverter {
    80
    81      private String[] patterns;
    82      private String displayPatterns;
    83      private Locale locale;
    84      private TimeZone timeZone;
    85      private boolean useLocaleFormat;
    86
    87
    88      // ----------------------------------------------------------- Constructors
    89
    90      /***
    91       * Construct a Date/Time <i>Converter</i> that throws a
    92       * <code>ConversionException</code> if an error occurs.
    93       *
    94       * @param defaultType The default type this <code>Converter</code>
    95       * handles
    96       */
    97      public DateTimeConverter(Class defaultType) {
    98          super(defaultType);
    99      }
    100
    101     /***
    102      * Construct a Date/Time <i>Converter</i> that returns a default
    103      * value if an error occurs.
    104      *
    105      * @param defaultType The default type this <code>Converter</code>
    106      * handles
    107      * @param defaultValue The default value to be returned
    108      * if the value to be converted is missing or an error
    109      * occurs converting the value.
    110      */
    111     public DateTimeConverter(Class defaultType, Object defaultValue) {
    112         super(defaultType, defaultValue);
    113     }
    114
    115
    116     // --------------------------------------------------------- Public Methods
    117
    118     /***
    119      * Indicate whether conversion should use a format/pattern or not.
    120      *
    121      * @param useLocaleFormat <code>true</code> if the format
    122      * for the locale should be used, otherwise <code>false</code>
    123      */
    124     public void setUseLocaleFormat(boolean useLocaleFormat) {
    125         this.useLocaleFormat = useLocaleFormat;
    126     }
    127
    128     /***
    129      * Return the Time Zone to use when converting dates
    130      * (or <code>null</code> if none specified.
    131      *
    132      * @return The Time Zone.
    133      */
    134     public TimeZone getTimeZone() {
    135         return timeZone;
    136     }
    137
    138     /***
    139      * Set the Time Zone to use when converting dates.
    140      *
    141      * @param timeZone The Time Zone.
    142      */
    143     public void setTimeZone(TimeZone timeZone) {
    144         this.timeZone = timeZone;
    145     }
    146
    147     /***
    148      * Return the Locale for the <i>Converter</i>
    149      * (or <code>null</code> if none specified).
    150      *
    151      * @return The locale to use for conversion
    152      */
    153     public Locale getLocale() {
    154         return locale;
    155     }
    156
    157     /***
    158      * Set the Locale for the <i>Converter</i>.
    159      *
    160      * @param locale The Locale.
    161      */
    162     public void setLocale(Locale locale) {
    163         this.locale = locale;
    164         setUseLocaleFormat(true);
    165     }
    166
    167     /***
    168      * Set a date format pattern to use to convert
    169      * dates to/from a <code>java.lang.String</code>.
    170      *
    171      * @see SimpleDateFormat
    172      * @param pattern The format pattern.
    173      */
    174     public void setPattern(String pattern) {
    175         setPatterns(new String[] {pattern});
    176     }
    177
    178     /***
    179      * Return the date format patterns used to convert
    180      * dates to/from a <code>java.lang.String</code>
    181      * (or <code>null</code> if none specified).
    182      *
    183      * @see SimpleDateFormat
    184      * @return Array of format patterns.
    185      */
    186     public String[] getPatterns() {
    187         return patterns;
    188     }
    189
    190     /***
    191      * Set the date format patterns to use to convert
    192      * dates to/from a <code>java.lang.String</code>.
    193      *
    194      * @see SimpleDateFormat
    195      * @param patterns Array of format patterns.
    196      */
    197     public void setPatterns(String[] patterns) {
    198         this.patterns = patterns;
    199         if (patterns != null && patterns.length > 1) {
    200             StringBuffer buffer = new StringBuffer();
    201             for (int i = 0; i < patterns.length; i++) {
    202                 if (i > 0) {
    203                     buffer.append(", ");
    204                 }
    205                 buffer.append(patterns[i]);
    206             }
    207             displayPatterns = buffer.toString();
    208         }
    209         setUseLocaleFormat(true);
    210     }
    211
    212     // ------------------------------------------------------ Protected Methods
    213
    214     /***
    215      * Convert an input Date/Calendar object into a String.
    216      * <p>
    217      * <b>N.B.</b>If the converter has been configured to with
    218      * one or more patterns (using <code>setPatterns()</code>), then
    219      * the first pattern will be used to format the date into a String.
    220      * Otherwise the default <code>DateFormat</code> for the default locale
    221      * (and <i>style</i> if configured) will be used.
    222      *
    223      * @param value The input value to be converted
    224      * @return the converted String value.
    225      * @throws Throwable if an error occurs converting to a String
    226      */
    227     protected String convertToString(Object value) throws Throwable {
    228
    229         Date date = null;
    230         if (value instanceof Date) {
    231             date = (Date)value;
    232         } else if (value instanceof Calendar) {
    233             date = ((Calendar)value).getTime();
    234         } else if (value instanceof Long) {
    235             date = new Date(((Long)value).longValue());
    236         }
    237
    238         String result = null;
    239         if (useLocaleFormat && date != null) {
    240             DateFormat format = null;
    241             if (patterns != null && patterns.length > 0) {
    242                 format = getFormat(patterns[0]);
    243             } else {
    244                 format = getFormat(locale, timeZone);
    245             }
    246             logFormat("Formatting", format);
    247             result = format.format(date);
    248             if (log().isDebugEnabled()) {
    249                 log().debug("    Converted  to String using format '" + result + "'");
    250             }
    251         } else {
    252             result = value.toString();
    253             if (log().isDebugEnabled()) {
    254                 log().debug("    Converted  to String using toString() '" + result + "'");
    255              }
    256         }
    257         return result;
    258     }
    259
    260     /***
    261      * Convert the input object into a Date object of the
    262      * specified type.
    263      * <p>
    264      * This method handles conversions between the following
    265      * types:
    266      * <ul>
    267      *     <li><code>java.util.Date</code></li>
    268      *     <li><code>java.util.Calendar</code></li>
    269      *     <li><code>java.sql.Date</code></li>
    270      *     <li><code>java.sql.Time</code></li>
    271      *     <li><code>java.sql.Timestamp</code></li>
    272      * </ul>
    273      *
    274      * It also handles conversion from a <code>String</code> to
    275      * any of the above types.
    276      * <p>
    277      *
    278      * For <code>String</code> conversion, if the converter has been configured
    279      * with one or more patterns (using <code>setPatterns()</code>), then
    280      * the conversion is attempted with each of the specified patterns.
    281      * Otherwise the default <code>DateFormat</code> for the default locale
    282      * (and <i>style</i> if configured) will be used.
    283      *
    284      * @param targetType Data type to which this value should be converted.
    285      * @param value The input value to be converted.
    286      * @return The converted value.
    287      * @throws Exception if conversion cannot be performed successfully
    288      */
    289     protected Object convertToType(Class targetType, Object value) throws Exception {
    290
    291         Class sourceType = value.getClass();
    292
    293         // Handle java.sql.Timestamp
    294         if (value instanceof java.sql.Timestamp) {
    295
    296             // ---------------------- JDK 1.3 Fix ----------------------
    297             // N.B. Prior to JDK 1.4 the Timestamp's getTime() method
    298             //      didn't include the milliseconds. The following code
    299             //      ensures it works consistently accross JDK versions
    300             java.sql.Timestamp timestamp = (java.sql.Timestamp)value;
    301             long timeInMillis = ((timestamp.getTime() / 1000) * 1000);
    302             timeInMillis += timestamp.getNanos() / 1000000;
    303             // ---------------------- JDK 1.3 Fix ----------------------
    304             return toDate(targetType, timeInMillis);
    305         }
    306
    307         // Handle Date (includes java.sql.Date & java.sql.Time)
    308         if (value instanceof Date) {
    309             Date date = (Date)value;
    310             return toDate(targetType, date.getTime());
    311         }
    312
    313         // Handle Calendar
    314         if (value instanceof Calendar) {
    315             Calendar calendar = (Calendar)value;
    316             return toDate(targetType, calendar.getTime().getTime());
    317         }
    318
    319         // Handle Long
    320         if (value instanceof Long) {
    321             Long longObj = (Long)value;
    322             return toDate(targetType, longObj.longValue());
    323         }
    324
    325         // Convert all other types to String & handle
    326         String stringValue = value.toString().trim();
    327         if (stringValue.length() == 0) {
    328             return handleMissing(targetType);
    329         }
    330
    331         // Parse the Date/Time
    332         if (useLocaleFormat) {
    333             Calendar calendar = null;
    334             if (patterns != null && patterns.length > 0) {
    335                 calendar = parse(sourceType, targetType, stringValue);
    336             } else {
    337                 DateFormat format = getFormat(locale, timeZone);
    338                 calendar = parse(sourceType, targetType, stringValue, format);
    339             }
    340             if (Calendar.class.isAssignableFrom(targetType)) {
    341                 return calendar;
    342             } else {
    343                 return toDate(targetType, calendar.getTime().getTime());
    344             }
    345         }
    346
    347         // Default String conversion
    348         return toDate(targetType, stringValue);
    349
    350     }
    351
    352     /***
    353      * Convert a long value to the specified Date type for this
    354      * <i>Converter</i>.
    355      * <p>
    356      *
    357      * This method handles conversion to the following types:
    358      * <ul>
    359      *     <li><code>java.util.Date</code></li>
    360      *     <li><code>java.util.Calendar</code></li>
    361      *     <li><code>java.sql.Date</code></li>
    362      *     <li><code>java.sql.Time</code></li>
    363      *     <li><code>java.sql.Timestamp</code></li>
    364      * </ul>
    365      *
    366      * @param type The Date type to convert to
    367      * @param value The long value to convert.
    368      * @return The converted date value.
    369      */
    370     private Object toDate(Class type, long value) {
    371
    372         // java.util.Date
    373         if (type.equals(Date.class)) {
    374             return new Date(value);
    375         }
    376
    377         // java.sql.Date
    378         if (type.equals(java.sql.Date.class)) {
    379             return new java.sql.Date(value);
    380         }
    381
    382         // java.sql.Time
    383         if (type.equals(java.sql.Time.class)) {
    384             return new java.sql.Time(value);
    385         }
    386
    387         // java.sql.Timestamp
    388         if (type.equals(java.sql.Timestamp.class)) {
    389             return new java.sql.Timestamp(value);
    390         }
    391
    392         // java.util.Calendar
    393         if (type.equals(Calendar.class)) {
    394             Calendar calendar = null;
    395             if (locale == null && timeZone == null) {
    396                 calendar = Calendar.getInstance();
    397             } else if (locale == null) {
    398                 calendar = Calendar.getInstance(timeZone);
    399             } else if (timeZone == null) {
    400                 calendar = Calendar.getInstance(locale);
    401             } else {
    402                 calendar = Calendar.getInstance(timeZone, locale);
    403             }
    404             calendar.setTime(new Date(value));
    405             calendar.setLenient(false);
    406             return calendar;
    407         }
    408
    409         String msg = toString(getClass()) + " cannot handle conversion to '"
    410                    + toString(type) + "'";
    411         if (log().isWarnEnabled()) {
    412             log().warn("    " + msg);
    413         }
    414         throw new ConversionException(msg);
    415     }
    416
    417     /***
    418      * Default String to Date conversion.
    419      * <p>
    420      * This method handles conversion from a String to the following types:
    421      * <ul>
    422      *     <li><code>java.sql.Date</code></li>
    423      *     <li><code>java.sql.Time</code></li>
    424      *     <li><code>java.sql.Timestamp</code></li>
    425      * </ul>
    426      * <p>
    427      * <strong>N.B.</strong> No default String conversion
    428      * mechanism is provided for <code>java.util.Date</code>
    429      * and <code>java.util.Calendar</code> type.
    430      *
    431      * @param type The Number type to convert to
    432      * @param value The String value to convert.
    433      * @return The converted Number value.
    434      */
    435     private Object toDate(Class type, String value) {
    436         // java.sql.Date
    437         if (type.equals(java.sql.Date.class)) {
    438             try {
    439                 return java.sql.Date.valueOf(value);
    440             } catch (IllegalArgumentException e) {
    441                 throw new ConversionException(
    442                         "String must be in JDBC format [yyyy-MM-dd] to create a java.sql.Date");
    443             }
    444         }
    445
    446         // java.sql.Time
    447         if (type.equals(java.sql.Time.class)) {
    448             try {
    449                 return java.sql.Time.valueOf(value);
    450             } catch (IllegalArgumentException e) {
    451                 throw new ConversionException(
    452                         "String must be in JDBC format [HH:mm:ss] to create a java.sql.Time");
    453             }
    454         }
    455
    456         // java.sql.Timestamp
    457         if (type.equals(java.sql.Timestamp.class)) {
    458             try {
    459                 return java.sql.Timestamp.valueOf(value);
    460             } catch (IllegalArgumentException e) {
    461                 throw new ConversionException(
    462                         "String must be in JDBC format [yyyy-MM-dd HH:mm:ss.fffffffff] " +
    463                         "to create a java.sql.Timestamp");
    464             }
    465         }
    466
    467         String msg = toString(getClass()) + " does not support default String to '"
    468                    + toString(type) + "' conversion.";
    469         if (log().isWarnEnabled()) {
    470             log().warn("    " + msg);
    471             log().warn("    (N.B. Re-configure Converter or use alternative implementation)");
    472         }
    473         throw new ConversionException(msg);
    474     }
    475
    476     /***
    477      * Return a <code>DateFormat<code> for the Locale.
    478      * @param locale The Locale to create the Format with (may be null)
    479      * @param timeZone The Time Zone create the Format with (may be null)
    480      *
    481      * @return A Date Format.
    482      */
    483     protected DateFormat getFormat(Locale locale, TimeZone timeZone) {
    484         DateFormat format = null;
    485         if (locale == null) {
    486             format = DateFormat.getDateInstance(DateFormat.SHORT);
    487         } else {
    488             format = DateFormat.getDateInstance(DateFormat.SHORT, locale);
    489         }
    490         if (timeZone != null) {
    491             format.setTimeZone(timeZone);
    492         }
    493         return format;
    494     }
    495
    496     /***
    497      * Create a date format for the specified pattern.
    498      *
    499      * @param pattern The date pattern
    500      * @return The DateFormat
    501      */
    502     private DateFormat getFormat(String pattern) {
    503         DateFormat format = new SimpleDateFormat(pattern);
    504         if (timeZone != null) {
    505             format.setTimeZone(timeZone);
    506         }
    507         return format;
    508     }
    509
    510     /***
    511      * Parse a String date value using the set of patterns.
    512      *
    513      * @param sourceType The type of the value being converted
    514      * @param targetType The type to convert the value to.
    515      * @param value The String date value.
    516      *
    517      * @return The converted Date object.
    518      * @throws Exception if an error occurs parsing the date.
    519      */
    520     private Calendar parse(Class sourceType, Class targetType, String value) throws Exception {
    521         Exception firstEx = null;
    522         for (int i = 0; i < patterns.length; i++) {
    523             try {
    524                 DateFormat format = getFormat(patterns[i]);
    525                 Calendar calendar = parse(sourceType, targetType, value, format);
    526                 return calendar;
    527             } catch (Exception ex) {
    528                 if (firstEx == null) {
    529                     firstEx = ex;
    530                 }
    531             }
    532         }
    533         if (patterns.length > 1) {
    534             throw new ConversionException("Error converting '" + toString(sourceType) + "' to '" + toString(targetType)
    535                     + "' using  patterns '" + displayPatterns + "'");
    536         } else {
    537             throw firstEx;
    538         }
    539     }
    540
    541     /***
    542      * Parse a String into a <code>Calendar</code> object
    543      * using the specified <code>DateFormat</code>.
    544      *
    545      * @param sourceType The type of the value being converted
    546      * @param targetType The type to convert the value to
    547      * @param value The String date value.
    548      * @param format The DateFormat to parse the String value.
    549      *
    550      * @return The converted Calendar object.
    551      * @throws ConversionException if the String cannot be converted.
    552      */
    553     private Calendar parse(Class sourceType, Class targetType, String value, DateFormat format) {
    554         logFormat("Parsing", format);
    555         format.setLenient(false);
    556         ParsePosition pos = new ParsePosition(0);
    557         Date parsedDate = format.parse(value, pos); // ignore the result (use the Calendar)
    558         if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedDate == null) {
    559             String msg = "Error converting '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
    560             if (format instanceof SimpleDateFormat) {
    561                 msg += " using pattern '" + ((SimpleDateFormat)format).toPattern() + "'";
    562             }
    563             if (log().isDebugEnabled()) {
    564                 log().debug("    " + msg);
    565             }
    566             throw new ConversionException(msg);
    567         }
    568         Calendar calendar = format.getCalendar();
    569         return calendar;
    570     }
    571
    572     /***
    573      * Provide a String representation of this date/time converter.
    574      *
    575      * @return A String representation of this date/time converter
    576      */
    577     public String toString() {
    578         StringBuffer buffer = new StringBuffer();
    579         buffer.append(toString(getClass()));
    580         buffer.append("[UseDefault=");
    581         buffer.append(isUseDefault());
    582         buffer.append(", UseLocaleFormat=");
    583         buffer.append(useLocaleFormat);
    584         if (displayPatterns != null) {
    585             buffer.append(", Patterns={");
    586             buffer.append(displayPatterns);
    587             buffer.append('}');
    588         }
    589         if (locale != null) {
    590             buffer.append(", Locale=");
    591             buffer.append(locale);
    592         }
    593         if (timeZone != null) {
    594             buffer.append(", TimeZone=");
    595             buffer.append(timeZone);
    596         }
    597         buffer.append(']');
    598         return buffer.toString();
    599     }
    600
    601     /***
    602      * Log the <code>DateFormat<code> creation.
    603      * @param action The action the format is being used for
    604      * @param format The Date format
    605      */
    606     private void logFormat(String action, DateFormat format) {
    607         if (log().isDebugEnabled()) {
    608             StringBuffer buffer = new StringBuffer(45);
    609             buffer.append("    ");
    610             buffer.append(action);
    611             buffer.append(" with Format");
    612             if (format instanceof SimpleDateFormat) {
    613                 buffer.append("[");
    614                 buffer.append(((SimpleDateFormat)format).toPattern());
    615                 buffer.append("]");
    616             }
    617             buffer.append(" for ");
    618             if (locale == null) {
    619                 buffer.append("default locale");
    620             } else {
    621                 buffer.append("locale[");
    622                 buffer.append(locale);
    623                 buffer.append("]");
    624             }
    625             if (timeZone != null) {
    626                 buffer.append(", TimeZone[");
    627                 buffer.append(timeZone);
    628                 buffer.append("]");
    629             }
    630             log().debug(buffer.toString());
    631         }
    632     }
    633 }