001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.utils;
014
015import static org.apache.juneau.internal.DateUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017import static org.apache.juneau.internal.ObjectUtils.*;
018
019import java.text.*;
020import java.util.*;
021import java.util.concurrent.*;
022
023import javax.xml.bind.*;
024
025/**
026 * Utility class for converting {@link Calendar} and {@link Date} objects to common serialized forms.
027 */
028public class CalendarUtils {
029
030   private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
031
032   /**
033    * Valid conversion formats.
034    */
035   public static enum Format {
036
037      /**
038       * Transform to ISO8601 date-time-local strings.
039       *
040       * <h5 class='section'>Example Output:</h5>
041       * <ul>
042       *    <li><js>"2001-07-04T15:30:45"</js>
043       * </ul>
044       *
045       * <h5 class='section'>Example input:</h5>
046       * <ul>
047       *    <li><js>"2001-07-04T15:30:45"</js>
048       *    <li><js>"2001-07-04T15:30:45.1"</js>
049       *    <li><js>"2001-07-04T15:30"</js>
050       *    <li><js>"2001-07-04"</js>
051       *    <li><js>"2001-07"</js>
052       *    <li><js>"2001"</js>
053       * </ul>
054       */
055      ISO8601_DTL,
056
057      /**
058       * Transform to ISO8601 date-time strings.
059       *
060       * <h5 class='section'>Example Output:</h5>
061       * <ul>
062       *    <li><js>"2001-07-04T15:30:45-05:00"</js>
063       *    <li><js>"2001-07-04T15:30:45Z"</js>
064       * </ul>
065       *
066       * <h5 class='section'>Example input:</h5>
067       * <ul>
068       *    <li><js>"2001-07-04T15:30:45-05:00"</js>
069       *    <li><js>"2001-07-04T15:30:45Z"</js>
070       *    <li><js>"2001-07-04T15:30:45.1Z"</js>
071       *    <li><js>"2001-07-04T15:30Z"</js>
072       *    <li><js>"2001-07-04"</js>
073       *    <li><js>"2001-07"</js>
074       *    <li><js>"2001"</js>
075       * </ul>
076       */
077      ISO8601_DT,
078
079      /**
080       * Same as {@link CalendarUtils.Format#ISO8601_DT}, except always serializes in GMT.
081       *
082       * <h5 class='section'>Example Output:</h5>
083       * <js>"2001-07-04T15:30:45Z"</js>
084       */
085      ISO8601_DTZ,
086
087      /**
088       * Same as {@link CalendarUtils.Format#ISO8601_DT} except serializes to millisecond precision.
089       *
090       * <h5 class='section'>Example Output:</h5>
091       * <js>"2001-07-04T15:30:45.123Z"</js>
092       */
093      ISO8601_DTP,
094
095      /**
096       * Same as {@link CalendarUtils.Format#ISO8601_DTZ} except serializes to millisecond precision.
097       *
098       * <h5 class='section'>Example Output:</h5>
099       * <js>"2001-07-04T15:30:45.123"</js>
100       */
101      ISO8601_DTPZ,
102
103      /**
104       * ISO8601 date only.
105       *
106       * <h5 class='section'>Example Output:</h5>
107       * <js>"2001-07-04"</js>
108       */
109      ISO8601_D,
110
111      /**
112       * Transform to {@link String Strings} using the {@code Date.toString()} method.
113       *
114       * <h5 class='section'>Example Output:</h5>
115       * <ul>
116       *    <li><js>"Wed Jul 04 15:30:45 EST 2001"</js>
117       * </ul>
118       */
119      TO_STRING,
120
121      /**
122       * Transform to RFC2822 date-time strings.
123       *
124       * <h5 class='section'>Example Output:</h5>
125       * <ul>
126       *    <li><js>"Sat, 03 Mar 2001 10:11:12 +0000"</js> <jc>// en_US</jc>
127       *    <li><js>"土, 03 3 2001 10:11:12 +0000"</js> <jc>// ja_JP</jc>
128       *    <li><js>"토, 03 3월 2001 10:11:12 +0000"</js> <jc>// ko_KR</jc>
129       * </ul>
130       */
131      RFC2822_DT,
132
133      /**
134       * Same as {@link CalendarUtils.Format#RFC2822_DT}, except always serializes in GMT.
135       *
136       * <h5 class='section'>Example Output:</h5>
137       * <ul>
138       *    <li><js>"Sat, 03 Mar 2001 10:11:12 GMT"</js> <jc>// en_US</jc>
139       *    <li><js>"土, 03 3 2001 10:11:12 GMT"</js> <jc>// ja_JP</jc>
140       *    <li><js>"토, 03 3월 2001 10:11:12 GMT"</js> <jc>// ko_KR</jc>
141       * </ul>
142       */
143      RFC2822_DTZ,
144
145      /**
146       * Transform to RFC2822 date strings.
147       *
148       * <h5 class='section'>Example Output:</h5>
149       * <ul>
150       *    <li><js>"03 Mar 2001"</js> <jc>// en_US</jc>
151       *    <li><js>"03 3 2001"</js> <jc>// ja_JP</jc>
152       *    <li><js>"03 3월 2001"</js> <jc>// ko_KR</jc>
153       * </ul>
154       */
155      RFC2822_D,
156
157      /**
158       * Transform to simple <js>"yyyy/MM/dd HH:mm:ss"</js> date-time strings.
159       *
160       * <h5 class='section'>Example Output:</h5>
161       * <ul>
162       *    <li><js>"2001/03/03 10:11:12"</js>
163       * </ul>
164       */
165      SIMPLE_DT,
166
167      /**
168       * Transform to simple <js>"yyyy/MM/dd"</js> date strings.
169       *
170       * <h5 class='section'>Example Output:</h5>
171       * <ul>
172       *    <li><js>"2001/03/03"</js>
173       * </ul>
174       */
175      SIMPLE_D,
176
177      /**
178       * Transform to simple <js>"HH:mm:ss"</js> time strings.
179       *
180       * <h5 class='section'>Example Output:</h5>
181       * <ul>
182       *    <li><js>"10:11:12"</js>
183       * </ul>
184       */
185      SIMPLE_T,
186
187      /**
188       * Transform to {@link DateFormat#FULL} date strings.
189       *
190       * <h5 class='section'>Example Output:</h5>
191       * <ul>
192       *    <li><js>"Saturday, March 3, 2001"</js> <jc>// en_US</jc>
193       *    <li><js>"2001年3月3日"</js> <jc>// ja_JP</jc>
194       *    <li><js>"2001년 3월 3일 토요일"</js> <jc>// ko_KR</jc>
195       * </ul>
196       */
197      FULL_D,
198
199      /**
200       * Transform to {@link DateFormat#LONG} date strings.
201       *
202       * <h5 class='section'>Example Output:</h5>
203       * <ul>
204       *    <li><js>"March 3, 2001"</js> <jc>// en_US</jc>
205       *    <li><js>"2001/03/03"</js> <jc>// ja_JP</jc>
206       *    <li><js>"2001년 3월 3일 (토)"</js> <jc>// ko_KR</jc>
207       * </ul>
208       */
209      LONG_D,
210
211      /**
212       * Transform to {@link DateFormat#MEDIUM} date strings.
213       *
214       * <h5 class='section'>Example Output:</h5>
215       * <ul>
216       *    <li><js>"Mar 3, 2001"</js> <jc>// en_US</jc>
217       *    <li><js>"2001/03/03"</js> <jc>// ja_JP</jc>
218       *    <li><js>"2001. 3. 3"</js> <jc>// ko_KR</jc>
219       * </ul>
220       */
221      MEDIUM_D,
222
223      /**
224       * Transform to {@link DateFormat#SHORT} date strings.
225       *
226       * <h5 class='section'>Example Output:</h5>
227       * <ul>
228       *    <li><js>"3/3/01"</js> <jc>// en_US</jc>
229       *    <li><js>"01/03/03"</js> <jc>// ja_JP</jc>
230       *    <li><js>"01. 3. 3"</js> <jc>// ko_KR</jc>
231       * </ul>
232       */
233      SHORT_D,
234
235      /**
236       * Transform to {@link DateFormat#FULL} time strings.
237       *
238       * <h5 class='section'>Example Output:</h5>
239       * <ul>
240       *    <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc>
241       *    <li><js>"10時11分12秒 GMT"</js> <jc>// ja_JP</jc>
242       *    <li><js>"오전 10시 11분 12초 GMT"</js> <jc>// ko_KR</jc>
243       * </ul>
244       */
245      FULL_T,
246
247      /**
248       * Transform to {@link DateFormat#LONG} time strings.
249       *
250       * <h5 class='section'>Example Output:</h5>
251       * <ul>
252       *    <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc>
253       *    <li><js>"10:11:12 GMT"</js> <jc>// ja_JP</jc>
254       *    <li><js>"오전 10시 11분 12초"</js> <jc>// ko_KR</jc>
255       * </ul>
256       */
257      LONG_T,
258
259      /**
260       * Transform to {@link DateFormat#MEDIUM} time strings.
261       *
262       * <h5 class='section'>Example Output:</h5>
263       * <ul>
264       *    <li><js>"10:11:12 AM"</js> <jc>// en_US</jc>
265       *    <li><js>"10:11:12"</js> <jc>// ja_JP</jc>
266       *    <li><js>"오전 10:11:12"</js> <jc>// ko_KR</jc>
267       * </ul>
268       */
269      MEDIUM_T,
270
271      /**
272       * Transform to {@link DateFormat#SHORT} time strings.
273       *
274       * <h5 class='section'>Example Output:</h5>
275       * <ul>
276       *    <li><js>"10:11 AM"</js> <jc>// en_US</jc>
277       *    <li><js>"10:11 AM"</js> <jc>// ja_JP</jc>
278       *    <li><js>"오전 10:11"</js> <jc>// ko_KR</jc>
279       * </ul>
280       */
281      SHORT_T,
282
283      /**
284       * Transform to {@link DateFormat#FULL} date-time strings.
285       *
286       * <h5 class='section'>Example Output:</h5>
287       * <ul>
288       *    <li><js>"Saturday, March 3, 2001 10:11:12 AM GMT"</js> <jc>// en_US</jc>
289       *    <li><js>"2001年3月3日 10時11分12秒 GMT"</js> <jc>// ja_JP</jc>
290       *    <li><js>"2001년 3월 3일 토요일 오전 10시 11분 12초 GMT"</js> <jc>// ko_KR</jc>
291       * </ul>
292       */
293      FULL_DT,
294
295      /**
296       * Transform to {@link DateFormat#LONG} date-time strings.
297       *
298       * <h5 class='section'>Example Output:</h5>
299       * <ul>
300       *    <li><js>"March 3, 2001 10:11:12 AM GMT"</js> <jc>// en_US</jc>
301       *    <li><js>"2001/03/03 10:11:12 GMT"</js> <jc>// ja_JP</jc>
302       *    <li><js>"2001년 3월 3일 (토) 오전 10시 11분 12초"</js> <jc>// ko_KR</jc>
303       * </ul>
304       */
305      LONG_DT,
306
307      /**
308       * Transform to {@link DateFormat#MEDIUM} date-time strings.
309       *
310       * <h5 class='section'>Example Output:</h5>
311       * <ul>
312       *    <li><js>"Mar 3, 2001 10:11:12 AM"</js> <jc>// en_US</jc>
313       *    <li><js>"2001/03/03 10:11:12"</js> <jc>// ja_JP</jc>
314       *    <li><js>"2001. 3. 3 오전 10:11:12"</js> <jc>// ko_KR</jc>
315       * </ul>
316       */
317      MEDIUM_DT,
318
319      /**
320       * Transform to {@link DateFormat#SHORT} date-time strings.
321       *
322       * <h5 class='section'>Example Output:</h5>
323       * <ul>
324       *    <li><js>"3/3/01 10:11 AM"</js> <jc>// en_US</jc>
325       *    <li><js>"01/03/03 10:11"</js> <jc>// ja_JP</jc>
326       *    <li><js>"01. 3. 3 오전 10:11"</js> <jc>// ko_KR</jc>
327       * </ul>
328       */
329      SHORT_DT
330   }
331
332   private static ThreadLocal<Map<DateFormatKey,DateFormat>> patternCache = new ThreadLocal<>();
333
334   static class DateFormatKey {
335      final CalendarUtils.Format format;
336      final Locale locale;
337      final TimeZone timeZone;
338      final int hashCode;
339
340      DateFormatKey(CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
341         this.format = format;
342         this.locale = locale;
343         this.timeZone = timeZone;
344         this.hashCode = format.hashCode() + locale.hashCode() + timeZone.hashCode();
345      }
346
347      @Override
348      public int hashCode() {
349         return hashCode;
350      }
351
352      @Override
353      public boolean equals(Object o) {
354         return (o instanceof DateFormatKey) && eq(this, (DateFormatKey)o, (x,y)->eq(x.format, y.format) && eq(x.locale, y.locale) && eq(x.timeZone, y.timeZone));
355      }
356   }
357
358
359   private static DateFormat getFormat(CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
360
361      if (locale == null)
362         locale = Locale.getDefault();
363
364      if (timeZone == null)
365         timeZone = TimeZone.getDefault();
366
367      DateFormatKey key = new DateFormatKey(format, locale, timeZone);
368
369      Map<DateFormatKey,DateFormat> m1 = patternCache.get();
370      if (m1 == null) {
371         m1 = new ConcurrentHashMap<>();
372         patternCache.set(m1);
373      }
374
375      DateFormat df = m1.get(key);
376
377      if (df == null) {
378         String p = null;
379         switch (format) {
380            case ISO8601_DTL: p = "yyyy-MM-dd'T'HH:mm:ss"; break;
381            case ISO8601_D: p = "yyyy-MM-dd"; break;
382            case TO_STRING: p = "EEE MMM dd HH:mm:ss zzz yyyy"; break;
383            case RFC2822_DT: p = "EEE, dd MMM yyyy HH:mm:ss Z"; break;
384            case RFC2822_DTZ: p = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; break;
385            case RFC2822_D: p = "dd MMM yyyy"; break;
386            case SIMPLE_DT: p = "yyyy/MM/dd HH:mm:ss"; break;
387            case SIMPLE_D: p = "yyyy/MM/dd"; break;
388            case SIMPLE_T: p = "HH:mm:ss"; break;
389            case FULL_D: df = DateFormat.getDateInstance(DateFormat.FULL, locale); break;
390            case LONG_D: df = DateFormat.getDateInstance(DateFormat.LONG, locale); break;
391            case MEDIUM_D: df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); break;
392            case SHORT_D: df = DateFormat.getDateInstance(DateFormat.SHORT, locale); break;
393            case FULL_T: df = DateFormat.getTimeInstance(DateFormat.FULL, locale); break;
394            case LONG_T: df = DateFormat.getTimeInstance(DateFormat.LONG, locale); break;
395            case MEDIUM_T: df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); break;
396            case SHORT_T: df = DateFormat.getTimeInstance(DateFormat.SHORT, locale); break;
397            case FULL_DT: df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale); break;
398            case LONG_DT: df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); break;
399            case MEDIUM_DT: df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); break;
400            case SHORT_DT: df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); break;
401            default: return null;
402         }
403         if (p != null) {
404            df = new SimpleDateFormat(p, locale);
405         }
406         if (df != null)
407            df.setTimeZone(timeZone);
408         m1.put(key, df);
409      }
410
411      return df;
412   }
413
414   /**
415    * Converts the specified calendar to a string of the specified format.
416    *
417    * @param c The calendar to serialize.
418    * @param format The date format.
419    * @param locale The locale to use.  If <jk>null</jk>, uses {@link Locale#getDefault()}.
420    * @param timeZone The time zone to use.  If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
421    * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>.
422    */
423   public static final String serialize(Calendar c, CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
424      if (c == null)
425         return null;
426      if (timeZone == null)
427         timeZone = c.getTimeZone();
428      switch(format) {
429         case ISO8601_DTL:
430         case ISO8601_D:
431         case RFC2822_D:
432         case RFC2822_DT:
433         case TO_STRING:
434         case FULL_D:
435         case FULL_DT:
436         case FULL_T:
437         case LONG_D:
438         case LONG_DT:
439         case LONG_T:
440         case MEDIUM_D:
441         case MEDIUM_DT:
442         case MEDIUM_T:
443         case SHORT_D:
444         case SHORT_DT:
445         case SHORT_T:
446         case SIMPLE_D:
447         case SIMPLE_DT:
448         case SIMPLE_T:
449            return serializeFromDateFormat(c.getTime(), format, locale, timeZone);
450         case ISO8601_DT:
451            return DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
452         case ISO8601_DTP:
453            String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
454            return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
455         case ISO8601_DTZ:
456            if (c.getTimeZone().getRawOffset() != 0) {
457               Calendar c2 = Calendar.getInstance(GMT);
458               c2.setTime(c.getTime());
459               c = c2;
460            }
461            return DatatypeConverter.printDateTime(c);
462         case ISO8601_DTPZ:
463            if (c.getTimeZone().getRawOffset() != 0) {
464               Calendar c2 = Calendar.getInstance(GMT);
465               c2.setTime(c.getTime());
466               c = c2;
467            }
468            s = DatatypeConverter.printDateTime(c);
469            return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
470         case RFC2822_DTZ:
471            return serializeFromDateFormat(c.getTime(), format, locale, GMT);
472      default:
473         break;
474      }
475      return null;
476   }
477
478   /**
479    * Converts the specified date to a string of the specified format.
480    *
481    * @param format The date format.
482    * @param d The date to serialize.
483    * @param locale The locale to use.  If <jk>null</jk>, uses {@link Locale#getDefault()}.
484    * @param timeZone The time zone to use.  If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
485    * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>.
486    */
487   public static final String serialize(Date d, CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
488      if (d == null)
489         return null;
490      if (timeZone == null)
491         timeZone = TimeZone.getDefault();
492      switch(format) {
493         case ISO8601_DTL:
494         case ISO8601_D:
495         case RFC2822_D:
496         case RFC2822_DT:
497         case TO_STRING:
498         case FULL_D:
499         case FULL_DT:
500         case FULL_T:
501         case LONG_D:
502         case LONG_DT:
503         case LONG_T:
504         case MEDIUM_D:
505         case MEDIUM_DT:
506         case MEDIUM_T:
507         case SHORT_D:
508         case SHORT_DT:
509         case SHORT_T:
510         case SIMPLE_D:
511         case SIMPLE_DT:
512         case SIMPLE_T:
513            return serializeFromDateFormat(d, format, locale, timeZone);
514         case ISO8601_DT:
515            Calendar c = new GregorianCalendar();
516            c.setTime(d);
517            c.setTimeZone(timeZone);
518            return DatatypeConverter.printDateTime(c);
519         case ISO8601_DTP:
520            c = new GregorianCalendar();
521            c.setTime(d);
522            c.setTimeZone(timeZone);
523            String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
524            return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
525         case ISO8601_DTZ:
526            c = new GregorianCalendar();
527            c.setTime(d);
528            c.setTimeZone(GMT);
529            return DatatypeConverter.printDateTime(c);
530         case ISO8601_DTPZ:
531            c = new GregorianCalendar();
532            c.setTime(d);
533            c.setTimeZone(GMT);
534            s = DatatypeConverter.printDateTime(c);
535            return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
536         case RFC2822_DTZ:
537            return serializeFromDateFormat(d, format, locale, GMT);
538      }
539      return null;
540   }
541
542
543   /**
544    * Converts the specified serialized date back into a {@link Calendar} object.
545    *
546    * @param format The date format.
547    * @param in The serialized date.
548    * @param locale
549    *    The locale to use.
550    *    If <jk>null</jk>, uses {@link Locale#getDefault()}.
551    * @param timeZone
552    *    The timezone to assume if input string doesn't contain timezone info.
553    *    If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
554    * @return The date as a {@link Calendar}, or <jk>null</jk> if the input was <jk>null</jk> or empty.
555    * @throws java.text.ParseException Malformed input encountered.
556    */
557   public static final Calendar parseCalendar(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws java.text.ParseException {
558      if (isEmpty(in))
559         return null;
560      if (timeZone == null)
561         timeZone = TimeZone.getDefault();
562      Date d = null;
563      switch(format) {
564
565         // These use DatatypeConverter to parse the date.
566         case ISO8601_DTL:
567         case ISO8601_DT:
568         case ISO8601_DTZ:
569         case ISO8601_DTP:
570         case ISO8601_DTPZ:
571         case ISO8601_D:
572            return DatatypeConverter.parseDateTime(toValidISO8601DT(in));
573
574         // These don't specify timezones, so we have to assume the timezone is whatever is specified.
575         case RFC2822_D:
576         case SIMPLE_DT:
577         case SIMPLE_D:
578         case SIMPLE_T:
579         case FULL_D:
580         case LONG_D:
581         case MEDIUM_D:
582         case SHORT_D:
583         case MEDIUM_T:
584         case SHORT_T:
585         case MEDIUM_DT:
586         case SHORT_DT:
587            d = getFormat(format, locale, GMT).parse(in);
588            d.setTime(d.getTime() - timeZone.getRawOffset());
589            break;
590
591         // This is always in GMT.
592         case RFC2822_DTZ:
593            DateFormat f  = getFormat(format, locale, GMT);
594            d = f.parse(in);
595            break;
596
597         // These specify timezones in the strings, so we don't use the specified timezone.
598         case TO_STRING:
599         case FULL_DT:
600         case FULL_T:
601         case LONG_DT:
602         case LONG_T:
603         case RFC2822_DT:
604            d = getFormat(format, locale, timeZone).parse(in);
605            break;
606      }
607      if (d == null)
608         return null;
609      Calendar c = new GregorianCalendar();
610      c.setTime(d);
611      c.setTimeZone(timeZone);
612      return c;
613   }
614
615   /**
616    * Converts the specified serialized date back into a {@link Date} object.
617    *
618    * @param format The date format.
619    * @param in The serialized date.
620    * @param locale
621    *    The locale to use.
622    *    If <jk>null</jk>, uses {@link Locale#getDefault()}.
623    * @param timeZone
624    *    The timezone to assume if input string doesn't contain timezone info.
625    *    If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
626    * @return The date as a {@link Date}, or <jk>null</jk> if the input was <jk>null</jk> or empty.
627    * @throws java.text.ParseException Malformed input encountered.
628    */
629   public static final Date parseDate(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws java.text.ParseException {
630      if (isEmpty(in))
631         return null;
632      if (timeZone == null)
633         timeZone = TimeZone.getDefault();
634      switch(format) {
635
636         // These use DatatypeConverter to parse the date.
637         case ISO8601_DTL:
638         case ISO8601_D:
639         case ISO8601_DT:
640         case ISO8601_DTZ:
641         case ISO8601_DTP:
642         case ISO8601_DTPZ:
643            return DatatypeConverter.parseDateTime(toValidISO8601DT(in)).getTime();
644
645         // These don't specify timezones, so we have to assume the timezone is whatever is specified.
646         case FULL_D:
647         case LONG_D:
648         case MEDIUM_D:
649         case MEDIUM_DT:
650         case MEDIUM_T:
651         case RFC2822_D:
652         case SHORT_D:
653         case SHORT_DT:
654         case SHORT_T:
655         case SIMPLE_D:
656         case SIMPLE_DT:
657         case SIMPLE_T:
658            return getFormat(format, locale, timeZone).parse(in);
659
660         // This is always in GMT.
661         case RFC2822_DTZ:
662            Date d = getFormat(format, locale, TimeZone.getDefault()).parse(in);
663            d.setTime(d.getTime() + TimeZone.getDefault().getRawOffset());
664            return d;
665
666         // These specify timezones in the strings, so we don't use the specified timezone.
667         case TO_STRING:
668         case FULL_DT:
669         case FULL_T:
670         case LONG_DT:
671         case LONG_T:
672         case RFC2822_DT:
673            return getFormat(format, locale, timeZone).parse(in);
674
675      }
676      return null;
677   }
678
679   private static String serializeFromDateFormat(Date date, CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
680      DateFormat df = getFormat(format, locale, timeZone);
681      String s = df.format(date);
682      return s;
683   }
684
685   private static Calendar setTimeZone(Calendar c, TimeZone tz) {
686      if (tz != null && ! tz.equals(c.getTimeZone())) {
687         c = (Calendar)c.clone();
688         c.setTimeZone(tz);
689      }
690      return c;
691   }
692}