1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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
294 if (value instanceof java.sql.Timestamp) {
295
296
297
298
299
300 java.sql.Timestamp timestamp = (java.sql.Timestamp)value;
301 long timeInMillis = ((timestamp.getTime() / 1000) * 1000);
302 timeInMillis += timestamp.getNanos() / 1000000;
303
304 return toDate(targetType, timeInMillis);
305 }
306
307
308 if (value instanceof Date) {
309 Date date = (Date)value;
310 return toDate(targetType, date.getTime());
311 }
312
313
314 if (value instanceof Calendar) {
315 Calendar calendar = (Calendar)value;
316 return toDate(targetType, calendar.getTime().getTime());
317 }
318
319
320 if (value instanceof Long) {
321 Long longObj = (Long)value;
322 return toDate(targetType, longObj.longValue());
323 }
324
325
326 String stringValue = value.toString().trim();
327 if (stringValue.length() == 0) {
328 return handleMissing(targetType);
329 }
330
331
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
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
373 if (type.equals(Date.class)) {
374 return new Date(value);
375 }
376
377
378 if (type.equals(java.sql.Date.class)) {
379 return new java.sql.Date(value);
380 }
381
382
383 if (type.equals(java.sql.Time.class)) {
384 return new java.sql.Time(value);
385 }
386
387
388 if (type.equals(java.sql.Timestamp.class)) {
389 return new java.sql.Timestamp(value);
390 }
391
392
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
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
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
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);
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 }