1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.transform;
17
18 import java.sql.Time;
19 import java.sql.Timestamp;
20 import java.text.DateFormat;
21 import java.text.ParseException;
22 import java.text.ParsePosition;
23 import java.util.Calendar;
24 import java.util.Date;
25 import java.util.Locale;
26
27 import org.apache.commons.configuration.Configuration;
28 import org.apache.commons.configuration.MapConfiguration;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 /**
33 * <p>
34 * An abstract base class for date transformer objects.
35 * </p>
36 * <p>
37 * Date transformers know how to handle certain kinds of date formats. They can
38 * <ul>
39 * <li>validate a string entered by a user to verify that it contains a valid
40 * date according to the supported format</li>
41 * <li>perform certain semantic checks, e.g. whether the date is in the past or
42 * future</li>
43 * <li>transform a valid string into a <code>java.util.Date</code> object</li>
44 * <li>transform a <code>java.util.Date</code> object into a string
45 * representation.</li>
46 * </ul>
47 * </p>
48 * <p>
49 * This base class already implements the major part of the required
50 * functionality. Concrete sub classes are responsible of creating an
51 * appropriate <code>DateFormat</code> object that is able to parse the
52 * specific date format.
53 * </p>
54 * <p>
55 * There are some properties for customizing the parsing of date strings. These
56 * properties can be set either through the set methods provided by this class
57 * or using a <code><properties></code> section in the builder script
58 * that declares the transformer. The following properties are supported: <table
59 * border="1">
60 * <tr>
61 * <th>Property</th>
62 * <th>Description</th>
63 * <th>Default</th>
64 * </tr>
65 * <tr>
66 * <td valign="top">style</td>
67 * <td>Defines the style of the date. This can be one of the style constants
68 * declared by the <code>java.text.DateFormat</code> class like
69 * <code>SHORT</code> or <code>FULL</code>.</td>
70 * <td valign="top">SHORT</td>
71 * </tr>
72 * <tr>
73 * <td valign="top">lenient</td>
74 * <td>Specifies the lenient mode for parsing dates. The lenient flag has the
75 * same meaning as described in the documentation of the
76 * <code>java.text.DateFormat</code> class and controls how strict the parsing
77 * process is. Note that lenient mode is turned off per default.</td>
78 * <td valign="top">false</td>
79 * </tr>
80 * <tr>
81 * <td valign="top">referenceDate</td>
82 * <td>With this property a reference date can be specified that is used for
83 * testing semantic correctness. For instance, if one of the <code>after</code>
84 * or <code>before</code> flags described below are set, it can be tested
85 * whether the entered date is after or before this reference date. The property
86 * must be a string conforming to one of the formats supported by
87 * <code>java.sql.Timestamp</code>, <code>java.sql.Date</code>, or
88 * <code>java.sql.Time</code>.</td>
89 * <td valign="top">current date</td>
90 * </tr>
91 * <tr>
92 * <td valign="top">after</td>
93 * <td>If this boolean flag is set, the entered date must be after the
94 * reference date.</td>
95 * <td valign="top">false</td>
96 * </tr>
97 * <tr>
98 * <td valign="top">before</td>
99 * <td>If this boolean flag is set, the entered date must be before the
100 * reference date. Note that the properties <code>before</code> and
101 * <code>after</code> are mutual exclusive.</td>
102 * <td valign="top">false</td>
103 * </tr>
104 * <tr>
105 * <td valign="top">equal</td>
106 * <td>This flag is evaluated only if <code>after</code> or
107 * <code>before</code> is <b>true</b>. In this case, it controls whether the
108 * reference date is included in the comparison. So a comparison can be
109 * specified whether the entered date is before or equal a reference date.</td>
110 * <td valign="top">false</td>
111 * </tr>
112 * </table>
113 * </p>
114 * <p>
115 * Depending on the performed validations this validator implementation can
116 * create a bunch of error messages. The following table lists all supported
117 * error messages: <table border="1">
118 * <tr>
119 * <th>Message key</th>
120 * <th>Description</th>
121 * <th>Parameters</th>
122 * </tr>
123 * <tr>
124 * <td valign="top"><code>{@value ValidationMessageConstants#ERR_INVALID_DATE}</code></td>
125 * <td valign="top">The passed in string cannot be parsed to a date object.</td>
126 * <td valign="top">{0} = the date string</td>
127 * </tr>
128 * <tr>
129 * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_AFTER}</code></td>
130 * <td valign="top">The entered date must be after the reference date.</td>
131 * <td valign="top">{0} = the (formatted) reference date</td>
132 * </tr>
133 * <tr>
134 * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_AFTER_EQUAL}</code></td>
135 * <td valign="top">The entered date must be after or equal the reference date.</td>
136 * <td valign="top">{0} = the (formatted) reference date</td>
137 * </tr>
138 * <tr>
139 * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_BEFORE}</code></td>
140 * <td valign="top">The entered date must be before the reference date.</td>
141 * <td valign="top">{0} = the (formatted) reference date</td>
142 * </tr>
143 * <tr>
144 * <td valign="top"><code>{@value ValidationMessageConstants#ERR_DATE_BEFORE_EQUAL}</code></td>
145 * <td valign="top">The entered date must be before or equal the reference
146 * date.</td>
147 * <td valign="top">{0} = the (formatted) reference date</td>
148 * </tr>
149 * </table>
150 * </p>
151 * <p>
152 * This class implements both the <code>{@link Transformer}</code> and the
153 * <code>{@link Validator}</code> interfaces. The <code>Transformer</code>
154 * implementation can work in both directions: if a <code>Date</code> object
155 * is passed in, it will format the date to a string using the specified format.
156 * Otherwise the passed in object is tried to be converted to a date.
157 * </p>
158 * <p>
159 * Instances can be shared between multiple input components. It is especially
160 * possible to use an instance as both (read and write) transformer and
161 * validator for an input component at the same time (provided that the same
162 * properties are used). However the class is not thread-safe.
163 * </p>
164 *
165 * @author Oliver Heger
166 * @version $Id: DateTransformerBase.java 205 2012-01-29 18:29:57Z oheger $
167 * @see ValidationMessageConstants
168 */
169 public abstract class DateTransformerBase implements Transformer, Validator
170 {
171 /** Constant for the style property. */
172 protected static final String PROP_STYLE = "style";
173
174 /** Constant for the lenient property. */
175 protected static final String PROP_LENIENT = "lenient";
176
177 /** Constant for the referenceDate property. */
178 protected static final String PROP_REFERENCE_DATE = "referenceDate";
179
180 /** Constant for the before property. */
181 protected static final String PROP_BEFORE = "before";
182
183 /** Constant for the after property. */
184 protected static final String PROP_AFTER = "after";
185
186 /** Constant for the equal property. */
187 protected static final String PROP_EQUAL = "equal";
188
189 /** An array with the calendar fields used for a date component. */
190 private static final int[] CALENDAR_DATE_FIELDS = {
191 Calendar.YEAR, Calendar.MONTH, Calendar.DATE
192 };
193
194 /** An array with the calendar fields used for a time component. */
195 private static final int[] CALENDAR_TIME_FIELDS = {
196 Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND,
197 Calendar.MILLISECOND
198 };
199
200 /** The logger. */
201 private final Log log = LogFactory.getLog(getClass());
202
203 /**
204 * Stores the internally used reference date that has been converted into a
205 * date object.
206 */
207 private Date internalReferenceDate;
208
209 /** Stores the reference date for comparisons. */
210 private String referenceDate;
211
212 /** Stores the style of the date. */
213 private int dateStyle;
214
215 /** Stores the lenient flag. */
216 private boolean lenient;
217
218 /** Stores the before flag. */
219 private boolean before;
220
221 /** Stores the after flag. */
222 private boolean after;
223
224 /** Stores the equal flag. */
225 private boolean equal;
226
227 /**
228 * Creates a new instance of <code>DateTransformerBase</code>.
229 */
230 protected DateTransformerBase()
231 {
232 setStyle(DateFormat.SHORT);
233 }
234
235 /**
236 * Returns the style for the date to be parsed.
237 *
238 * @return the date style
239 */
240 public int getStyle()
241 {
242 return dateStyle;
243 }
244
245 /**
246 * Sets the style for the date to be parsed. This is one of the style
247 * constants defined by the <code>DateFormat</code> class, e.g.
248 * <code>DateFormat.SHORT</code> or <code>DateFormat.MEDIUM</code>.
249 *
250 * @param dateStyle the style for the date
251 */
252 public void setStyle(int dateStyle)
253 {
254 this.dateStyle = dateStyle;
255 }
256
257 /**
258 * Returns the lenient flag.
259 *
260 * @return the lenient flag
261 */
262 public boolean isLenient()
263 {
264 return lenient;
265 }
266
267 /**
268 * Sets the lenient flag.
269 *
270 * @param lenient the lenient flag
271 */
272 public void setLenient(boolean lenient)
273 {
274 this.lenient = lenient;
275 }
276
277 /**
278 * Returns the reference date.
279 *
280 * @return the reference date
281 */
282 public String getReferenceDate()
283 {
284 return referenceDate;
285 }
286
287 /**
288 * Sets the reference date. This date will be used for before or after
289 * comparisons. The date is set as a string. This string must conform to the
290 * format supported by the date classes in the <code>java.sql</code>
291 * package.
292 *
293 * @param referenceDate the reference date
294 * @throws IllegalArgumentException if the date has not the expected format
295 */
296 public void setReferenceDate(String referenceDate)
297 {
298 this.referenceDate = referenceDate;
299 internalReferenceDate = (referenceDate == null) ? null
300 : transformSqlDate(referenceDate);
301 }
302
303 /**
304 * Returns the before flag.
305 *
306 * @return the before flag
307 */
308 public boolean isBefore()
309 {
310 return before;
311 }
312
313 /**
314 * Sets the before flag. If set, the validate method checks whether the
315 * passed in date is before the reference date.
316 *
317 * @param before the before flag
318 */
319 public void setBefore(boolean before)
320 {
321 this.before = before;
322 }
323
324 /**
325 * Returns the after flag.
326 *
327 * @return the after flag
328 */
329 public boolean isAfter()
330 {
331 return after;
332 }
333
334 /**
335 * Sets the after flag. If set, the validate method checks whether the
336 * passed in date is after the reference date.
337 *
338 * @param after the after flag
339 */
340 public void setAfter(boolean after)
341 {
342 this.after = after;
343 }
344
345 /**
346 * Returns the equal flag.
347 *
348 * @return the equal flag
349 */
350 public boolean isEqual()
351 {
352 return equal;
353 }
354
355 /**
356 * Sets the equal flag. This flag is evaluated if one of the
357 * <code>before</code> or <code>after</code> flags is set. In this case
358 * the reference date is included into the comparison.
359 *
360 * @param equal the value of the equal flag
361 */
362 public void setEqual(boolean equal)
363 {
364 this.equal = equal;
365 }
366
367 /**
368 * Transforms the specified object. This implementation is able to transform
369 * a date in string form to a <code>java.util.Date</code> object. If the
370 * date is invalid, an exception is thrown. The method does not perform any
371 * additional validity checks. This means that any valid date will be
372 * returned, even if it conflicts with a reference date.
373 *
374 * @param o the object to be transformed
375 * @param ctx the transformer context
376 * @return the transformed object
377 * @throws Exception if an error occurs
378 */
379 public Object transform(Object o, TransformerContext ctx) throws Exception
380 {
381 if (o instanceof Date)
382 {
383 return transformToString((Date) o, ctx);
384 }
385 else
386 {
387 return transformToDate(o, ctx);
388 }
389 }
390
391 /**
392 * Validates the passed in object. This implementation transforms the object
393 * into a string and checks whether it represents a valid date. If the
394 * <code>before</code> or <code>after</code> flags have been set, the
395 * date will also be compared to a reference date. A <b>null</b> input will
396 * be considered valid.
397 *
398 * @param o the object to be validated
399 * @param ctx the transformer context
400 * @return an object with the results of the validation
401 */
402 public ValidationResult isValid(Object o, TransformerContext ctx)
403 {
404 String strDate = checkDefinedDate(o);
405 if (strDate == null)
406 {
407 return DefaultValidationResult.VALID;
408 }
409
410 Configuration config = new MapConfiguration(ctx.properties());
411 DateFormat fmt = initializeFormat(ctx.getLocale(), config);
412 try
413 {
414 Date dt = transformDate(strDate, fmt);
415 return isDateValid(dt, fmt, ctx, config);
416 }
417 catch (ParseException pex)
418 {
419 return errorResult(ValidationMessageConstants.ERR_INVALID_DATE,
420 ctx, strDate);
421 }
422 }
423
424 /**
425 * Writes the given date part into the specified date object. This method
426 * will write the date component into a combined date/time object leaving
427 * the time component untouched. This is useful for instance if a GUI has
428 * different input fields for the date and the time, but in the data model
429 * only a single <code>Date</code> object is used. For example, if the
430 * <code>dateTime</code> parameter has the value
431 * <code>2008-01-29 22:17:59</code> and <code>datePart</code> is
432 * <code>2008-02-05</code>, the result will be
433 * <code>2008-02-05 22:17:59</code>.
434 *
435 * @param dateTime the combined date/time object
436 * @param datePart the date part
437 * @return the changed date/time object
438 * @throws IllegalArgumentException if one of the date parameters is <b>null</b>
439 */
440 public static Date updateDatePart(Date dateTime, Date datePart)
441 {
442 return updateComponent(dateTime, datePart, CALENDAR_DATE_FIELDS);
443 }
444
445 /**
446 * Writes the given time part into the specified date object. This method
447 * will write the time component into a combined date/time object leaving
448 * the date component untouched. This is useful for instance if a GUI has
449 * different input fields for the date and the time, but in the data model
450 * only a single <code>Date</code> object is used. For example, if the
451 * <code>dateTime</code> parameter has the value
452 * <code>2008-01-29 22:17:59</code> and <code>timePart</code> is
453 * <code>10:22:05</code>, the result will be
454 * <code>2008-02-05 10:22:05</code>.
455 *
456 * @param dateTime the combined date/time object
457 * @param timePart the time part
458 * @return the changed date/time object
459 * @throws IllegalArgumentException if one of the date parameters is <b>null</b>
460 */
461 public static Date updateTimePart(Date dateTime, Date timePart)
462 {
463 return updateComponent(dateTime, timePart, CALENDAR_TIME_FIELDS);
464 }
465
466 /**
467 * Performs a transformation to a date object. Tries to parse the string
468 * representation of the parsed in object.
469 *
470 * @param o the object to be transformed
471 * @param ctx the transformer context
472 * @return the transformed object
473 * @throws Exception if an error occurs
474 */
475 protected Object transformToDate(Object o, TransformerContext ctx)
476 throws Exception
477 {
478 String strDate = checkDefinedDate(o);
479 if (strDate == null)
480 {
481 return null;
482 }
483
484 Configuration config = new MapConfiguration(ctx.properties());
485 return transformDate(strDate, initializeFormat(ctx.getLocale(), config));
486 }
487
488 /**
489 * Performs a transformation from a date to string. This method is called if
490 * the object to be transformed is already a date. In this case this
491 * transformer class works in the opposite direction.
492 *
493 * @param dt the date to be transformed
494 * @param ctx the transformer context
495 * @return the transformed object
496 * @throws Exception if an error occurs
497 */
498 protected Object transformToString(Date dt, TransformerContext ctx)
499 throws Exception
500 {
501 Configuration config = new MapConfiguration(ctx.properties());
502 return initializeFormat(ctx.getLocale(), config).format(dt);
503 }
504
505 /**
506 * Returns the reference date to be used. If a reference date is defined in
507 * the configuration, it is used. Otherwise the internally set reference
508 * date will be returned. If no reference date has been set, the
509 * <code>getDefaultReferenceDate()</code> method is called.
510 *
511 * @param config the configuration with the current properties
512 * @return the reference date
513 * @throws IllegalArgumentException if the reference date is in an incorrect
514 * format
515 */
516 protected Date getReferenceDateProperty(Configuration config)
517 {
518 String strDate = config.getString(PROP_REFERENCE_DATE);
519 if (strDate != null)
520 {
521 return transformSqlDate(strDate);
522 }
523 else
524 {
525 return (internalReferenceDate != null) ? internalReferenceDate
526 : getDefaultReferenceDate();
527 }
528 }
529
530 /**
531 * Creates a new default reference date. This method is invoked when before
532 * or after comparisons have to be performed, but no reference date has been
533 * set. This implementation returns a <code>Date</code> object for the
534 * current date (only date, no time portion).
535 *
536 * @return the default reference date
537 */
538 protected Date getDefaultReferenceDate()
539 {
540 Calendar cal = Calendar.getInstance();
541 cal.set(Calendar.HOUR_OF_DAY, 0);
542 cal.clear(Calendar.MINUTE);
543 cal.clear(Calendar.SECOND);
544 cal.clear(Calendar.MILLISECOND);
545 return cal.getTime();
546 }
547
548 /**
549 * Transforms a date in string form to a date object. This method expects
550 * that the date is in a <code>java.sql</code> compatible format.
551 *
552 * @param strDate the date as a string
553 * @return the converted date object
554 * @throws IllegalArgumentException if the date cannot be converted
555 */
556 protected Date transformSqlDate(String strDate)
557 {
558 if (log.isDebugEnabled())
559 {
560 log.debug("Trying to transform date string " + strDate);
561 }
562 try
563 {
564 return Timestamp.valueOf(strDate);
565 }
566 catch (IllegalArgumentException iex)
567 {
568 // no timestamp
569 log.debug("Not a time stamp.");
570 }
571
572 try
573 {
574 return java.sql.Date.valueOf(strDate);
575 }
576 catch (IllegalArgumentException iex)
577 {
578 // no date
579 log.debug("Not a date.");
580 }
581
582 return Time.valueOf(strDate);
583 }
584
585 /**
586 * Parses the specified date string. This implementation uses the passed in
587 * <code>DateFormat</code> object for this purpose.
588 *
589 * @param date the date to be parsed
590 * @param fmt the <code>DateFormat</code> to be used
591 * @return the parsed date
592 * @throws ParseException if the date cannot be parsed
593 */
594 protected Date transformDate(String date, DateFormat fmt)
595 throws ParseException
596 {
597 ParsePosition ppos = new ParsePosition(0);
598 Date result = fmt.parse(date, ppos);
599 if (ppos.getErrorIndex() >= 0 || ppos.getIndex() < date.length())
600 {
601 throw new ParseException("Invalid date: " + date, ppos.getIndex());
602 }
603 return result;
604 }
605
606 /**
607 * Returns an initialized format object. This implementation calls
608 * <code>createFormat()</code> for obtaining a new format object. Then the
609 * object is initialized based on the currently set properties.
610 *
611 * @param locale the locale
612 * @param config the properties associated with the current context
613 * @return the initialized format object
614 */
615 protected DateFormat initializeFormat(Locale locale, Configuration config)
616 {
617 DateFormat fmt = createFormat(locale, config.getInt(PROP_STYLE,
618 getStyle()), config);
619 fmt.setLenient(config.getBoolean(PROP_LENIENT, isLenient()));
620 return fmt;
621 }
622
623 /**
624 * Checks the specified date. This method is called by
625 * <code>isValid()</code> if the entered date is syntactically correct. It
626 * checks for semantic correctness, e.g. whether the date is in correct
627 * relation to the reference date.
628 *
629 * @param date the date to check
630 * @param fmt the date format object to be used
631 * @param ctx the transformer context
632 * @param config the configuration with the properties
633 * @return a <code>ValidationResult</code> object with the result of the
634 * validation
635 */
636 protected ValidationResult isDateValid(Date date, DateFormat fmt,
637 TransformerContext ctx, Configuration config)
638 {
639 String errorKey = null;
640
641 if (config.getBoolean(PROP_AFTER, isAfter())
642 || config.getBoolean(PROP_BEFORE, isBefore()))
643 {
644 int comp = date.compareTo(getReferenceDateProperty(config));
645 boolean eqProp = config.getBoolean(PROP_EQUAL, isEqual());
646 boolean eq = eqProp && comp == 0;
647
648 if (config.getBoolean(PROP_AFTER, isAfter()))
649 {
650 if (comp <= 0 && !eq)
651 {
652 errorKey = eqProp ? ValidationMessageConstants.ERR_DATE_AFTER_EQUAL
653 : ValidationMessageConstants.ERR_DATE_AFTER;
654 }
655 }
656
657 if (config.getBoolean(PROP_BEFORE, isBefore()))
658 {
659 if (comp >= 0 && !eq)
660 {
661 errorKey = eqProp ? ValidationMessageConstants.ERR_DATE_BEFORE_EQUAL
662 : ValidationMessageConstants.ERR_DATE_BEFORE;
663 }
664 }
665 }
666
667 if (errorKey != null)
668 {
669 // an error has occurred => create an error message
670 // with the properly formatted reference date as parameter
671 String refDate = fmt.format(getReferenceDateProperty(config));
672 return errorResult(errorKey, ctx, refDate);
673 }
674 else
675 {
676 return DefaultValidationResult.VALID;
677 }
678 }
679
680 /**
681 * Creates a validation result if an error occurred.
682 *
683 * @param errorKey the key of the error message
684 * @param ctx the transformer context
685 * @param params optional parameters for the error message
686 * @return the validation result with this error
687 */
688 protected ValidationResult errorResult(String errorKey,
689 TransformerContext ctx, Object... params)
690 {
691 DefaultValidationResult vr = new DefaultValidationResult.Builder()
692 .addValidationMessage(
693 ctx.getValidationMessageHandler().getValidationMessage(
694 ctx, errorKey, params)).build();
695 return vr;
696 }
697
698 /**
699 * Creates a <code>DateFormat</code> object for parsing dates of the
700 * supported format. Concrete sub classes have to return an appropriate
701 * instance of <code>DateFormat</code> (an implementation will probably
702 * call the correct <code>getXXXInstance()</code> factory method of
703 * <code>DateFormat</code>).
704 *
705 * @param locale the locale
706 * @param style the style to be used
707 * @param config a configuration object for accessing the current properties
708 * @return the <code>DateFormat</code> object to be used for parsing
709 */
710 protected abstract DateFormat createFormat(Locale locale, int style,
711 Configuration config);
712
713 /**
714 * Checks whether the date is defined. It is defined if it is not <b>null</b>
715 * and no empty string.
716 *
717 * @param o the object to be checked
718 * @return the object transformed to a string (<b>null</b> if the
719 * parameter is undefined)
720 */
721 private String checkDefinedDate(Object o)
722 {
723 if (o == null)
724 {
725 return null;
726 }
727
728 String strDate = String.valueOf(o);
729 return (strDate.length() < 1) ? null : strDate;
730 }
731
732 /**
733 * Converts a date object to a calendar. If the date is undefined, an
734 * exception will be thrown.
735 *
736 * @param dt the date
737 * @return the calendar
738 * @throws IllegalArgumentException if the date parameter is undefined
739 */
740 private static Calendar dateToCalendar(Date dt)
741 {
742 if (dt == null)
743 {
744 throw new IllegalArgumentException(
745 "Date parameter must not be null!");
746 }
747
748 Calendar cal = Calendar.getInstance();
749 cal.setTime(dt);
750 return cal;
751 }
752
753 /**
754 * Copies a set of fields from one calendar to another one.
755 *
756 * @param cal1 the target calendar
757 * @param cal2 the source calendar
758 * @param fields the fields to copy
759 */
760 private static void copyCalendarFields(Calendar cal1, Calendar cal2,
761 int[] fields)
762 {
763 for (int field : fields)
764 {
765 cal1.set(field, cal2.get(field));
766 }
767 }
768
769 /**
770 * Updates a component of a date/time object.
771 *
772 * @param dateTime the date/time object
773 * @param component the component
774 * @param fields the fields to be copied for the component
775 * @return the resulting date/time object
776 * @throws IllegalArgumentException if one of the dates is undefined
777 */
778 private static Date updateComponent(Date dateTime, Date component,
779 int[] fields)
780 {
781 Calendar cal1 = dateToCalendar(dateTime);
782 Calendar cal2 = dateToCalendar(component);
783 copyCalendarFields(cal1, cal2, fields);
784 return cal1.getTime();
785 }
786 }