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 * @throws Exception 425 */ 426 public static final String serialize(Calendar c, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 427 if (c == null) 428 return null; 429 if (timeZone == null) 430 timeZone = c.getTimeZone(); 431 switch(format) { 432 case ISO8601_DTL: 433 case ISO8601_D: 434 case RFC2822_D: 435 case RFC2822_DT: 436 case TO_STRING: 437 case FULL_D: 438 case FULL_DT: 439 case FULL_T: 440 case LONG_D: 441 case LONG_DT: 442 case LONG_T: 443 case MEDIUM_D: 444 case MEDIUM_DT: 445 case MEDIUM_T: 446 case SHORT_D: 447 case SHORT_DT: 448 case SHORT_T: 449 case SIMPLE_D: 450 case SIMPLE_DT: 451 case SIMPLE_T: 452 return serializeFromDateFormat(c.getTime(), format, locale, timeZone); 453 case ISO8601_DT: 454 return DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 455 case ISO8601_DTP: 456 String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 457 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 458 case ISO8601_DTZ: 459 if (c.getTimeZone().getRawOffset() != 0) { 460 Calendar c2 = Calendar.getInstance(GMT); 461 c2.setTime(c.getTime()); 462 c = c2; 463 } 464 return DatatypeConverter.printDateTime(c); 465 case ISO8601_DTPZ: 466 if (c.getTimeZone().getRawOffset() != 0) { 467 Calendar c2 = Calendar.getInstance(GMT); 468 c2.setTime(c.getTime()); 469 c = c2; 470 } 471 s = DatatypeConverter.printDateTime(c); 472 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 473 case RFC2822_DTZ: 474 return serializeFromDateFormat(c.getTime(), format, locale, GMT); 475 default: 476 break; 477 } 478 return null; 479 } 480 481 /** 482 * Converts the specified date to a string of the specified format. 483 * 484 * @param format The date format. 485 * @param d The date to serialize. 486 * @param locale The locale to use. If <jk>null</jk>, uses {@link Locale#getDefault()}. 487 * @param timeZone The time zone to use. If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 488 * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>. 489 * @throws Exception 490 */ 491 public static final String serialize(Date d, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 492 if (d == null) 493 return null; 494 if (timeZone == null) 495 timeZone = TimeZone.getDefault(); 496 switch(format) { 497 case ISO8601_DTL: 498 case ISO8601_D: 499 case RFC2822_D: 500 case RFC2822_DT: 501 case TO_STRING: 502 case FULL_D: 503 case FULL_DT: 504 case FULL_T: 505 case LONG_D: 506 case LONG_DT: 507 case LONG_T: 508 case MEDIUM_D: 509 case MEDIUM_DT: 510 case MEDIUM_T: 511 case SHORT_D: 512 case SHORT_DT: 513 case SHORT_T: 514 case SIMPLE_D: 515 case SIMPLE_DT: 516 case SIMPLE_T: 517 return serializeFromDateFormat(d, format, locale, timeZone); 518 case ISO8601_DT: 519 Calendar c = new GregorianCalendar(); 520 c.setTime(d); 521 c.setTimeZone(timeZone); 522 return DatatypeConverter.printDateTime(c); 523 case ISO8601_DTP: 524 c = new GregorianCalendar(); 525 c.setTime(d); 526 c.setTimeZone(timeZone); 527 String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 528 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 529 case ISO8601_DTZ: 530 c = new GregorianCalendar(); 531 c.setTime(d); 532 c.setTimeZone(GMT); 533 return DatatypeConverter.printDateTime(c); 534 case ISO8601_DTPZ: 535 c = new GregorianCalendar(); 536 c.setTime(d); 537 c.setTimeZone(GMT); 538 s = DatatypeConverter.printDateTime(c); 539 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 540 case RFC2822_DTZ: 541 return serializeFromDateFormat(d, format, locale, GMT); 542 } 543 return null; 544 } 545 546 547 /** 548 * Converts the specified serialized date back into a {@link Calendar} object. 549 * 550 * @param format The date format. 551 * @param in The serialized date. 552 * @param locale 553 * The locale to use. 554 * If <jk>null</jk>, uses {@link Locale#getDefault()}. 555 * @param timeZone 556 * The timezone to assume if input string doesn't contain timezone info. 557 * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 558 * @return The date as a {@link Calendar}, or <jk>null</jk> if the input was <jk>null</jk> or empty. 559 * @throws Exception 560 */ 561 public static final Calendar parseCalendar(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 562 if (isEmpty(in)) 563 return null; 564 if (timeZone == null) 565 timeZone = TimeZone.getDefault(); 566 Date d = null; 567 switch(format) { 568 569 // These use DatatypeConverter to parse the date. 570 case ISO8601_DTL: 571 case ISO8601_DT: 572 case ISO8601_DTZ: 573 case ISO8601_DTP: 574 case ISO8601_DTPZ: 575 case ISO8601_D: 576 return DatatypeConverter.parseDateTime(toValidISO8601DT(in)); 577 578 // These don't specify timezones, so we have to assume the timezone is whatever is specified. 579 case RFC2822_D: 580 case SIMPLE_DT: 581 case SIMPLE_D: 582 case SIMPLE_T: 583 case FULL_D: 584 case LONG_D: 585 case MEDIUM_D: 586 case SHORT_D: 587 case MEDIUM_T: 588 case SHORT_T: 589 case MEDIUM_DT: 590 case SHORT_DT: 591 d = getFormat(format, locale, GMT).parse(in); 592 d.setTime(d.getTime() - timeZone.getRawOffset()); 593 break; 594 595 // This is always in GMT. 596 case RFC2822_DTZ: 597 DateFormat f = getFormat(format, locale, GMT); 598 d = f.parse(in); 599 break; 600 601 // These specify timezones in the strings, so we don't use the specified timezone. 602 case TO_STRING: 603 case FULL_DT: 604 case FULL_T: 605 case LONG_DT: 606 case LONG_T: 607 case RFC2822_DT: 608 d = getFormat(format, locale, timeZone).parse(in); 609 break; 610 } 611 if (d == null) 612 return null; 613 Calendar c = new GregorianCalendar(); 614 c.setTime(d); 615 c.setTimeZone(timeZone); 616 return c; 617 } 618 619 /** 620 * Converts the specified serialized date back into a {@link Date} object. 621 * 622 * @param format The date format. 623 * @param in The serialized date. 624 * @param locale 625 * The locale to use. 626 * If <jk>null</jk>, uses {@link Locale#getDefault()}. 627 * @param timeZone 628 * The timezone to assume if input string doesn't contain timezone info. 629 * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 630 * @return The date as a {@link Date}, or <jk>null</jk> if the input was <jk>null</jk> or empty. 631 * @throws Exception 632 */ 633 public static final Date parseDate(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 634 if (isEmpty(in)) 635 return null; 636 if (timeZone == null) 637 timeZone = TimeZone.getDefault(); 638 switch(format) { 639 640 // These use DatatypeConverter to parse the date. 641 case ISO8601_DTL: 642 case ISO8601_D: 643 case ISO8601_DT: 644 case ISO8601_DTZ: 645 case ISO8601_DTP: 646 case ISO8601_DTPZ: 647 return DatatypeConverter.parseDateTime(toValidISO8601DT(in)).getTime(); 648 649 // These don't specify timezones, so we have to assume the timezone is whatever is specified. 650 case FULL_D: 651 case LONG_D: 652 case MEDIUM_D: 653 case MEDIUM_DT: 654 case MEDIUM_T: 655 case RFC2822_D: 656 case SHORT_D: 657 case SHORT_DT: 658 case SHORT_T: 659 case SIMPLE_D: 660 case SIMPLE_DT: 661 case SIMPLE_T: 662 return getFormat(format, locale, timeZone).parse(in); 663 664 // This is always in GMT. 665 case RFC2822_DTZ: 666 Date d = getFormat(format, locale, TimeZone.getDefault()).parse(in); 667 d.setTime(d.getTime() + TimeZone.getDefault().getRawOffset()); 668 return d; 669 670 // These specify timezones in the strings, so we don't use the specified timezone. 671 case TO_STRING: 672 case FULL_DT: 673 case FULL_T: 674 case LONG_DT: 675 case LONG_T: 676 case RFC2822_DT: 677 return getFormat(format, locale, timeZone).parse(in); 678 679 } 680 return null; 681 } 682 683 private static String serializeFromDateFormat(Date date, CalendarUtils.Format format, Locale locale, TimeZone timeZone) { 684 DateFormat df = getFormat(format, locale, timeZone); 685 String s = df.format(date); 686 return s; 687 } 688 689 private static Calendar setTimeZone(Calendar c, TimeZone tz) { 690 if (tz != null && ! tz.equals(c.getTimeZone())) { 691 c = (Calendar)c.clone(); 692 c.setTimeZone(tz); 693 } 694 return c; 695 } 696}