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