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='section'>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='section'>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}