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.*; 016 017import java.text.*; 018import java.util.*; 019import java.util.concurrent.*; 020 021import javax.xml.bind.*; 022 023import org.apache.juneau.internal.*; 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='topic'>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='topic'>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 if (o == null) 355 return false; 356 DateFormatKey key = (DateFormatKey)o; 357 return format.equals(key.format) && locale.equals(key.locale) && timeZone.equals(key.timeZone); 358 } 359 } 360 361 362 private static DateFormat getFormat(CalendarUtils.Format format, Locale locale, TimeZone timeZone) { 363 364 if (locale == null) 365 locale = Locale.getDefault(); 366 367 if (timeZone == null) 368 timeZone = TimeZone.getDefault(); 369 370 DateFormatKey key = new DateFormatKey(format, locale, timeZone); 371 372 Map<DateFormatKey,DateFormat> m1 = patternCache.get(); 373 if (m1 == null) { 374 m1 = new ConcurrentHashMap<>(); 375 patternCache.set(m1); 376 } 377 378 DateFormat df = m1.get(key); 379 380 if (df == null) { 381 String p = null; 382 switch (format) { 383 case ISO8601_DTL: p = "yyyy-MM-dd'T'HH:mm:ss"; break; 384 case ISO8601_D: p = "yyyy-MM-dd"; break; 385 case TO_STRING: p = "EEE MMM dd HH:mm:ss zzz yyyy"; break; 386 case RFC2822_DT: p = "EEE, dd MMM yyyy HH:mm:ss Z"; break; 387 case RFC2822_DTZ: p = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; break; 388 case RFC2822_D: p = "dd MMM yyyy"; break; 389 case SIMPLE_DT: p = "yyyy/MM/dd HH:mm:ss"; break; 390 case SIMPLE_D: p = "yyyy/MM/dd"; break; 391 case SIMPLE_T: p = "HH:mm:ss"; break; 392 case FULL_D: df = DateFormat.getDateInstance(DateFormat.FULL, locale); break; 393 case LONG_D: df = DateFormat.getDateInstance(DateFormat.LONG, locale); break; 394 case MEDIUM_D: df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); break; 395 case SHORT_D: df = DateFormat.getDateInstance(DateFormat.SHORT, locale); break; 396 case FULL_T: df = DateFormat.getTimeInstance(DateFormat.FULL, locale); break; 397 case LONG_T: df = DateFormat.getTimeInstance(DateFormat.LONG, locale); break; 398 case MEDIUM_T: df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); break; 399 case SHORT_T: df = DateFormat.getTimeInstance(DateFormat.SHORT, locale); break; 400 case FULL_DT: df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale); break; 401 case LONG_DT: df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); break; 402 case MEDIUM_DT: df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); break; 403 case SHORT_DT: df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); break; 404 default: return null; 405 } 406 if (p != null) { 407 df = new SimpleDateFormat(p, locale); 408 } 409 if (df != null) 410 df.setTimeZone(timeZone); 411 m1.put(key, df); 412 } 413 414 return df; 415 } 416 417 /** 418 * Converts the specified calendar to a string of the specified format. 419 * 420 * @param c The calendar to serialize. 421 * @param format The date format. 422 * @param locale The locale to use. If <jk>null</jk>, uses {@link Locale#getDefault()}. 423 * @param timeZone The time zone to use. If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 424 * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>. 425 * @throws Exception 426 */ 427 public static final String serialize(Calendar c, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 428 if (c == null) 429 return null; 430 if (timeZone == null) 431 timeZone = c.getTimeZone(); 432 switch(format) { 433 case ISO8601_DTL: 434 case ISO8601_D: 435 case RFC2822_D: 436 case RFC2822_DT: 437 case TO_STRING: 438 case FULL_D: 439 case FULL_DT: 440 case FULL_T: 441 case LONG_D: 442 case LONG_DT: 443 case LONG_T: 444 case MEDIUM_D: 445 case MEDIUM_DT: 446 case MEDIUM_T: 447 case SHORT_D: 448 case SHORT_DT: 449 case SHORT_T: 450 case SIMPLE_D: 451 case SIMPLE_DT: 452 case SIMPLE_T: 453 return serializeFromDateFormat(c.getTime(), format, locale, timeZone); 454 case ISO8601_DT: 455 return DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 456 case ISO8601_DTP: 457 String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 458 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 459 case ISO8601_DTZ: 460 if (c.getTimeZone().getRawOffset() != 0) { 461 Calendar c2 = Calendar.getInstance(GMT); 462 c2.setTime(c.getTime()); 463 c = c2; 464 } 465 return DatatypeConverter.printDateTime(c); 466 case ISO8601_DTPZ: 467 if (c.getTimeZone().getRawOffset() != 0) { 468 Calendar c2 = Calendar.getInstance(GMT); 469 c2.setTime(c.getTime()); 470 c = c2; 471 } 472 s = DatatypeConverter.printDateTime(c); 473 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 474 case RFC2822_DTZ: 475 return serializeFromDateFormat(c.getTime(), format, locale, GMT); 476 default: 477 break; 478 } 479 return null; 480 } 481 482 /** 483 * Converts the specified date to a string of the specified format. 484 * 485 * @param format The date format. 486 * @param d The date to serialize. 487 * @param locale The locale to use. If <jk>null</jk>, uses {@link Locale#getDefault()}. 488 * @param timeZone The time zone to use. If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 489 * @return The serialized date, or <jk>null</jk> if the calendar was <jk>null</jk>. 490 * @throws Exception 491 */ 492 public static final String serialize(Date d, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 493 if (d == null) 494 return null; 495 if (timeZone == null) 496 timeZone = TimeZone.getDefault(); 497 switch(format) { 498 case ISO8601_DTL: 499 case ISO8601_D: 500 case RFC2822_D: 501 case RFC2822_DT: 502 case TO_STRING: 503 case FULL_D: 504 case FULL_DT: 505 case FULL_T: 506 case LONG_D: 507 case LONG_DT: 508 case LONG_T: 509 case MEDIUM_D: 510 case MEDIUM_DT: 511 case MEDIUM_T: 512 case SHORT_D: 513 case SHORT_DT: 514 case SHORT_T: 515 case SIMPLE_D: 516 case SIMPLE_DT: 517 case SIMPLE_T: 518 return serializeFromDateFormat(d, format, locale, timeZone); 519 case ISO8601_DT: 520 Calendar c = new GregorianCalendar(); 521 c.setTime(d); 522 c.setTimeZone(timeZone); 523 return DatatypeConverter.printDateTime(c); 524 case ISO8601_DTP: 525 c = new GregorianCalendar(); 526 c.setTime(d); 527 c.setTimeZone(timeZone); 528 String s = DatatypeConverter.printDateTime(setTimeZone(c, timeZone)); 529 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 530 case ISO8601_DTZ: 531 c = new GregorianCalendar(); 532 c.setTime(d); 533 c.setTimeZone(GMT); 534 return DatatypeConverter.printDateTime(c); 535 case ISO8601_DTPZ: 536 c = new GregorianCalendar(); 537 c.setTime(d); 538 c.setTimeZone(GMT); 539 s = DatatypeConverter.printDateTime(c); 540 return String.format("%s.%03d%s", s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19)); 541 case RFC2822_DTZ: 542 return serializeFromDateFormat(d, format, locale, GMT); 543 } 544 return null; 545 } 546 547 548 /** 549 * Converts the specified serialized date back into a {@link Calendar} object. 550 * 551 * @param format The date format. 552 * @param in The serialized date. 553 * @param locale 554 * The locale to use. 555 * If <jk>null</jk>, uses {@link Locale#getDefault()}. 556 * @param timeZone 557 * The timezone to assume if input string doesn't contain timezone info. 558 * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 559 * @return The date as a {@link Calendar}, or <jk>null</jk> if the input was <jk>null</jk> or empty. 560 * @throws Exception 561 */ 562 public static final Calendar parseCalendar(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 563 if (StringUtils.isEmpty(in)) 564 return null; 565 if (timeZone == null) 566 timeZone = TimeZone.getDefault(); 567 Date d = null; 568 switch(format) { 569 570 // These use DatatypeConverter to parse the date. 571 case ISO8601_DTL: 572 case ISO8601_DT: 573 case ISO8601_DTZ: 574 case ISO8601_DTP: 575 case ISO8601_DTPZ: 576 case ISO8601_D: 577 return DatatypeConverter.parseDateTime(toValidISO8601DT(in)); 578 579 // These don't specify timezones, so we have to assume the timezone is whatever is specified. 580 case RFC2822_D: 581 case SIMPLE_DT: 582 case SIMPLE_D: 583 case SIMPLE_T: 584 case FULL_D: 585 case LONG_D: 586 case MEDIUM_D: 587 case SHORT_D: 588 case MEDIUM_T: 589 case SHORT_T: 590 case MEDIUM_DT: 591 case SHORT_DT: 592 d = getFormat(format, locale, GMT).parse(in); 593 d.setTime(d.getTime() - timeZone.getRawOffset()); 594 break; 595 596 // This is always in GMT. 597 case RFC2822_DTZ: 598 DateFormat f = getFormat(format, locale, GMT); 599 d = f.parse(in); 600 break; 601 602 // These specify timezones in the strings, so we don't use the specified timezone. 603 case TO_STRING: 604 case FULL_DT: 605 case FULL_T: 606 case LONG_DT: 607 case LONG_T: 608 case RFC2822_DT: 609 d = getFormat(format, locale, timeZone).parse(in); 610 break; 611 } 612 if (d == null) 613 return null; 614 Calendar c = new GregorianCalendar(); 615 c.setTime(d); 616 c.setTimeZone(timeZone); 617 return c; 618 } 619 620 /** 621 * Converts the specified serialized date back into a {@link Date} object. 622 * 623 * @param format The date format. 624 * @param in The serialized date. 625 * @param locale 626 * The locale to use. 627 * If <jk>null</jk>, uses {@link Locale#getDefault()}. 628 * @param timeZone 629 * The timezone to assume if input string doesn't contain timezone info. 630 * If <jk>null</jk>, uses {@link TimeZone#getDefault()}. 631 * @return The date as a {@link Date}, or <jk>null</jk> if the input was <jk>null</jk> or empty. 632 * @throws Exception 633 */ 634 public static final Date parseDate(String in, CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception { 635 if (StringUtils.isEmpty(in)) 636 return null; 637 if (timeZone == null) 638 timeZone = TimeZone.getDefault(); 639 switch(format) { 640 641 // These use DatatypeConverter to parse the date. 642 case ISO8601_DTL: 643 case ISO8601_D: 644 case ISO8601_DT: 645 case ISO8601_DTZ: 646 case ISO8601_DTP: 647 case ISO8601_DTPZ: 648 return DatatypeConverter.parseDateTime(toValidISO8601DT(in)).getTime(); 649 650 // These don't specify timezones, so we have to assume the timezone is whatever is specified. 651 case FULL_D: 652 case LONG_D: 653 case MEDIUM_D: 654 case MEDIUM_DT: 655 case MEDIUM_T: 656 case RFC2822_D: 657 case SHORT_D: 658 case SHORT_DT: 659 case SHORT_T: 660 case SIMPLE_D: 661 case SIMPLE_DT: 662 case SIMPLE_T: 663 return getFormat(format, locale, timeZone).parse(in); 664 665 // This is always in GMT. 666 case RFC2822_DTZ: 667 Date d = getFormat(format, locale, TimeZone.getDefault()).parse(in); 668 d.setTime(d.getTime() + TimeZone.getDefault().getRawOffset()); 669 return d; 670 671 // These specify timezones in the strings, so we don't use the specified timezone. 672 case TO_STRING: 673 case FULL_DT: 674 case FULL_T: 675 case LONG_DT: 676 case LONG_T: 677 case RFC2822_DT: 678 return getFormat(format, locale, timeZone).parse(in); 679 680 } 681 return null; 682 } 683 684 private static String serializeFromDateFormat(Date date, CalendarUtils.Format format, Locale locale, TimeZone timeZone) { 685 DateFormat df = getFormat(format, locale, timeZone); 686 String s = df.format(date); 687 return s; 688 } 689 690 private static Calendar setTimeZone(Calendar c, TimeZone tz) { 691 if (tz != null && ! tz.equals(c.getTimeZone())) { 692 c = (Calendar)c.clone(); 693 c.setTimeZone(tz); 694 } 695 return c; 696 } 697}