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}