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