001 /* 002 * Copyright 2001-2009 Stephen Colebourne 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.joda.time.format; 017 018 import java.io.IOException; 019 import java.io.Writer; 020 import java.text.DateFormat; 021 import java.text.SimpleDateFormat; 022 import java.util.HashMap; 023 import java.util.Locale; 024 import java.util.Map; 025 026 import org.joda.time.Chronology; 027 import org.joda.time.DateTime; 028 import org.joda.time.DateTimeZone; 029 import org.joda.time.ReadablePartial; 030 031 /** 032 * Factory that creates instances of DateTimeFormatter from patterns and styles. 033 * <p> 034 * Datetime formatting is performed by the {@link DateTimeFormatter} class. 035 * Three classes provide factory methods to create formatters, and this is one. 036 * The others are {@link ISODateTimeFormat} and {@link DateTimeFormatterBuilder}. 037 * <p> 038 * This class provides two types of factory: 039 * <ul> 040 * <li>{@link #forPattern(String) Pattern} provides a DateTimeFormatter based on 041 * a pattern string that is mostly compatible with the JDK date patterns. 042 * <li>{@link #forStyle(String) Style} provides a DateTimeFormatter based on a 043 * two character style, representing short, medium, long and full. 044 * </ul> 045 * <p> 046 * For example, to use a patterm: 047 * <pre> 048 * DateTime dt = new DateTime(); 049 * DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM, yyyy"); 050 * String str = fmt.print(dt); 051 * </pre> 052 * 053 * The pattern syntax is mostly compatible with java.text.SimpleDateFormat - 054 * time zone names cannot be parsed and a few more symbols are supported. 055 * All ASCII letters are reserved as pattern letters, which are defined as follows: 056 * <blockquote> 057 * <pre> 058 * Symbol Meaning Presentation Examples 059 * ------ ------- ------------ ------- 060 * G era text AD 061 * C century of era (>=0) number 20 062 * Y year of era (>=0) year 1996 063 * 064 * x weekyear year 1996 065 * w week of weekyear number 27 066 * e day of week number 2 067 * E day of week text Tuesday; Tue 068 * 069 * y year year 1996 070 * D day of year number 189 071 * M month of year month July; Jul; 07 072 * d day of month number 10 073 * 074 * a halfday of day text PM 075 * K hour of halfday (0~11) number 0 076 * h clockhour of halfday (1~12) number 12 077 * 078 * H hour of day (0~23) number 0 079 * k clockhour of day (1~24) number 24 080 * m minute of hour number 30 081 * s second of minute number 55 082 * S fraction of second number 978 083 * 084 * z time zone text Pacific Standard Time; PST 085 * Z time zone offset/id zone -0800; -08:00; America/Los_Angeles 086 * 087 * ' escape for text delimiter 088 * '' single quote literal ' 089 * </pre> 090 * </blockquote> 091 * The count of pattern letters determine the format. 092 * <p> 093 * <strong>Text</strong>: If the number of pattern letters is 4 or more, 094 * the full form is used; otherwise a short or abbreviated form is used if 095 * available. 096 * <p> 097 * <strong>Number</strong>: The minimum number of digits. Shorter numbers 098 * are zero-padded to this amount. 099 * <p> 100 * <strong>Year</strong>: Numeric presentation for year and weekyear fields 101 * are handled specially. For example, if the count of 'y' is 2, the year 102 * will be displayed as the zero-based year of the century, which is two 103 * digits. 104 * <p> 105 * <strong>Month</strong>: 3 or over, use text, otherwise use number. 106 * <p> 107 * <strong>Zone</strong>: 'Z' outputs offset without a colon, 'ZZ' outputs 108 * the offset with a colon, 'ZZZ' or more outputs the zone id. 109 * <p> 110 * <strong>Zone names</strong>: Time zone names ('z') cannot be parsed. 111 * <p> 112 * Any characters in the pattern that are not in the ranges of ['a'..'z'] 113 * and ['A'..'Z'] will be treated as quoted text. For instance, characters 114 * like ':', '.', ' ', '#' and '?' will appear in the resulting time text 115 * even they are not embraced within single quotes. 116 * <p> 117 * DateTimeFormat is thread-safe and immutable, and the formatters it returns 118 * are as well. 119 * 120 * @author Brian S O'Neill 121 * @author Maxim Zhao 122 * @since 1.0 123 * @see ISODateTimeFormat 124 * @see DateTimeFormatterBuilder 125 */ 126 public class DateTimeFormat { 127 128 /** Style constant for FULL. */ 129 static final int FULL = 0; // DateFormat.FULL 130 /** Style constant for LONG. */ 131 static final int LONG = 1; // DateFormat.LONG 132 /** Style constant for MEDIUM. */ 133 static final int MEDIUM = 2; // DateFormat.MEDIUM 134 /** Style constant for SHORT. */ 135 static final int SHORT = 3; // DateFormat.SHORT 136 /** Style constant for NONE. */ 137 static final int NONE = 4; 138 139 /** Type constant for DATE only. */ 140 static final int DATE = 0; 141 /** Type constant for TIME only. */ 142 static final int TIME = 1; 143 /** Type constant for DATETIME. */ 144 static final int DATETIME = 2; 145 146 /** Maps patterns to formatters, patterns don't vary by locale. */ 147 private static final Map<String, DateTimeFormatter> cPatternedCache = new HashMap<String, DateTimeFormatter>(7); 148 /** Maps patterns to formatters, patterns don't vary by locale. */ 149 private static final DateTimeFormatter[] cStyleCache = new DateTimeFormatter[25]; 150 151 //----------------------------------------------------------------------- 152 /** 153 * Factory to create a formatter from a pattern string. 154 * The pattern string is described above in the class level javadoc. 155 * It is very similar to SimpleDateFormat patterns. 156 * <p> 157 * The format may contain locale specific output, and this will change as 158 * you change the locale of the formatter. 159 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 160 * For example: 161 * <pre> 162 * DateTimeFormat.forPattern(pattern).withLocale(Locale.FRANCE).print(dt); 163 * </pre> 164 * 165 * @param pattern pattern specification 166 * @return the formatter 167 * @throws IllegalArgumentException if the pattern is invalid 168 */ 169 public static DateTimeFormatter forPattern(String pattern) { 170 return createFormatterForPattern(pattern); 171 } 172 173 /** 174 * Factory to create a format from a two character style pattern. 175 * <p> 176 * The first character is the date style, and the second character is the 177 * time style. Specify a character of 'S' for short style, 'M' for medium, 178 * 'L' for long, and 'F' for full. 179 * A date or time may be ommitted by specifying a style character '-'. 180 * <p> 181 * The returned formatter will dynamically adjust to the locale that 182 * the print/parse takes place in. Thus you just call 183 * {@link DateTimeFormatter#withLocale(Locale)} and the Short/Medium/Long/Full 184 * style for that locale will be output. For example: 185 * <pre> 186 * DateTimeFormat.forStyle(style).withLocale(Locale.FRANCE).print(dt); 187 * </pre> 188 * 189 * @param style two characters from the set {"S", "M", "L", "F", "-"} 190 * @return the formatter 191 * @throws IllegalArgumentException if the style is invalid 192 */ 193 public static DateTimeFormatter forStyle(String style) { 194 return createFormatterForStyle(style); 195 } 196 197 /** 198 * Returns the pattern used by a particular style and locale. 199 * <p> 200 * The first character is the date style, and the second character is the 201 * time style. Specify a character of 'S' for short style, 'M' for medium, 202 * 'L' for long, and 'F' for full. 203 * A date or time may be ommitted by specifying a style character '-'. 204 * 205 * @param style two characters from the set {"S", "M", "L", "F", "-"} 206 * @param locale locale to use, null means default 207 * @return the formatter 208 * @throws IllegalArgumentException if the style is invalid 209 * @since 1.3 210 */ 211 public static String patternForStyle(String style, Locale locale) { 212 DateTimeFormatter formatter = createFormatterForStyle(style); 213 if (locale == null) { 214 locale = Locale.getDefault(); 215 } 216 // Not pretty, but it works. 217 return ((StyleFormatter) formatter.getPrinter()).getPattern(locale); 218 } 219 220 //----------------------------------------------------------------------- 221 /** 222 * Creates a format that outputs a short date format. 223 * <p> 224 * The format will change as you change the locale of the formatter. 225 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 226 * 227 * @return the formatter 228 */ 229 public static DateTimeFormatter shortDate() { 230 return createFormatterForStyleIndex(SHORT, NONE); 231 } 232 233 /** 234 * Creates a format that outputs a short time format. 235 * <p> 236 * The format will change as you change the locale of the formatter. 237 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 238 * 239 * @return the formatter 240 */ 241 public static DateTimeFormatter shortTime() { 242 return createFormatterForStyleIndex(NONE, SHORT); 243 } 244 245 /** 246 * Creates a format that outputs a short datetime format. 247 * <p> 248 * The format will change as you change the locale of the formatter. 249 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 250 * 251 * @return the formatter 252 */ 253 public static DateTimeFormatter shortDateTime() { 254 return createFormatterForStyleIndex(SHORT, SHORT); 255 } 256 257 //----------------------------------------------------------------------- 258 /** 259 * Creates a format that outputs a medium date format. 260 * <p> 261 * The format will change as you change the locale of the formatter. 262 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 263 * 264 * @return the formatter 265 */ 266 public static DateTimeFormatter mediumDate() { 267 return createFormatterForStyleIndex(MEDIUM, NONE); 268 } 269 270 /** 271 * Creates a format that outputs a medium time format. 272 * <p> 273 * The format will change as you change the locale of the formatter. 274 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 275 * 276 * @return the formatter 277 */ 278 public static DateTimeFormatter mediumTime() { 279 return createFormatterForStyleIndex(NONE, MEDIUM); 280 } 281 282 /** 283 * Creates a format that outputs a medium datetime format. 284 * <p> 285 * The format will change as you change the locale of the formatter. 286 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 287 * 288 * @return the formatter 289 */ 290 public static DateTimeFormatter mediumDateTime() { 291 return createFormatterForStyleIndex(MEDIUM, MEDIUM); 292 } 293 294 //----------------------------------------------------------------------- 295 /** 296 * Creates a format that outputs a long date format. 297 * <p> 298 * The format will change as you change the locale of the formatter. 299 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 300 * 301 * @return the formatter 302 */ 303 public static DateTimeFormatter longDate() { 304 return createFormatterForStyleIndex(LONG, NONE); 305 } 306 307 /** 308 * Creates a format that outputs a long time format. 309 * <p> 310 * The format will change as you change the locale of the formatter. 311 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 312 * 313 * @return the formatter 314 */ 315 public static DateTimeFormatter longTime() { 316 return createFormatterForStyleIndex(NONE, LONG); 317 } 318 319 /** 320 * Creates a format that outputs a long datetime format. 321 * <p> 322 * The format will change as you change the locale of the formatter. 323 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 324 * 325 * @return the formatter 326 */ 327 public static DateTimeFormatter longDateTime() { 328 return createFormatterForStyleIndex(LONG, LONG); 329 } 330 331 //----------------------------------------------------------------------- 332 /** 333 * Creates a format that outputs a full date format. 334 * <p> 335 * The format will change as you change the locale of the formatter. 336 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 337 * 338 * @return the formatter 339 */ 340 public static DateTimeFormatter fullDate() { 341 return createFormatterForStyleIndex(FULL, NONE); 342 } 343 344 /** 345 * Creates a format that outputs a full time format. 346 * <p> 347 * The format will change as you change the locale of the formatter. 348 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 349 * 350 * @return the formatter 351 */ 352 public static DateTimeFormatter fullTime() { 353 return createFormatterForStyleIndex(NONE, FULL); 354 } 355 356 /** 357 * Creates a format that outputs a full datetime format. 358 * <p> 359 * The format will change as you change the locale of the formatter. 360 * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. 361 * 362 * @return the formatter 363 */ 364 public static DateTimeFormatter fullDateTime() { 365 return createFormatterForStyleIndex(FULL, FULL); 366 } 367 368 //----------------------------------------------------------------------- 369 /** 370 * Parses the given pattern and appends the rules to the given 371 * DateTimeFormatterBuilder. 372 * 373 * @param pattern pattern specification 374 * @throws IllegalArgumentException if the pattern is invalid 375 */ 376 static void appendPatternTo(DateTimeFormatterBuilder builder, String pattern) { 377 parsePatternTo(builder, pattern); 378 } 379 380 //----------------------------------------------------------------------- 381 /** 382 * Constructor. 383 * 384 * @since 1.1 (previously private) 385 */ 386 protected DateTimeFormat() { 387 super(); 388 } 389 390 //----------------------------------------------------------------------- 391 /** 392 * Parses the given pattern and appends the rules to the given 393 * DateTimeFormatterBuilder. 394 * 395 * @param pattern pattern specification 396 * @throws IllegalArgumentException if the pattern is invalid 397 * @see #forPattern 398 */ 399 private static void parsePatternTo(DateTimeFormatterBuilder builder, String pattern) { 400 int length = pattern.length(); 401 int[] indexRef = new int[1]; 402 403 for (int i=0; i<length; i++) { 404 indexRef[0] = i; 405 String token = parseToken(pattern, indexRef); 406 i = indexRef[0]; 407 408 int tokenLen = token.length(); 409 if (tokenLen == 0) { 410 break; 411 } 412 char c = token.charAt(0); 413 414 switch (c) { 415 case 'G': // era designator (text) 416 builder.appendEraText(); 417 break; 418 case 'C': // century of era (number) 419 builder.appendCenturyOfEra(tokenLen, tokenLen); 420 break; 421 case 'x': // weekyear (number) 422 case 'y': // year (number) 423 case 'Y': // year of era (number) 424 if (tokenLen == 2) { 425 boolean lenientParse = true; 426 427 // Peek ahead to next token. 428 if (i + 1 < length) { 429 indexRef[0]++; 430 if (isNumericToken(parseToken(pattern, indexRef))) { 431 // If next token is a number, cannot support 432 // lenient parse, because it will consume digits 433 // that it should not. 434 lenientParse = false; 435 } 436 indexRef[0]--; 437 } 438 439 // Use pivots which are compatible with SimpleDateFormat. 440 switch (c) { 441 case 'x': 442 builder.appendTwoDigitWeekyear 443 (new DateTime().getWeekyear() - 30, lenientParse); 444 break; 445 case 'y': 446 case 'Y': 447 default: 448 builder.appendTwoDigitYear(new DateTime().getYear() - 30, lenientParse); 449 break; 450 } 451 } else { 452 // Try to support long year values. 453 int maxDigits = 9; 454 455 // Peek ahead to next token. 456 if (i + 1 < length) { 457 indexRef[0]++; 458 if (isNumericToken(parseToken(pattern, indexRef))) { 459 // If next token is a number, cannot support long years. 460 maxDigits = tokenLen; 461 } 462 indexRef[0]--; 463 } 464 465 switch (c) { 466 case 'x': 467 builder.appendWeekyear(tokenLen, maxDigits); 468 break; 469 case 'y': 470 builder.appendYear(tokenLen, maxDigits); 471 break; 472 case 'Y': 473 builder.appendYearOfEra(tokenLen, maxDigits); 474 break; 475 } 476 } 477 break; 478 case 'M': // month of year (text and number) 479 if (tokenLen >= 3) { 480 if (tokenLen >= 4) { 481 builder.appendMonthOfYearText(); 482 } else { 483 builder.appendMonthOfYearShortText(); 484 } 485 } else { 486 builder.appendMonthOfYear(tokenLen); 487 } 488 break; 489 case 'd': // day of month (number) 490 builder.appendDayOfMonth(tokenLen); 491 break; 492 case 'a': // am/pm marker (text) 493 builder.appendHalfdayOfDayText(); 494 break; 495 case 'h': // clockhour of halfday (number, 1..12) 496 builder.appendClockhourOfHalfday(tokenLen); 497 break; 498 case 'H': // hour of day (number, 0..23) 499 builder.appendHourOfDay(tokenLen); 500 break; 501 case 'k': // clockhour of day (1..24) 502 builder.appendClockhourOfDay(tokenLen); 503 break; 504 case 'K': // hour of halfday (0..11) 505 builder.appendHourOfHalfday(tokenLen); 506 break; 507 case 'm': // minute of hour (number) 508 builder.appendMinuteOfHour(tokenLen); 509 break; 510 case 's': // second of minute (number) 511 builder.appendSecondOfMinute(tokenLen); 512 break; 513 case 'S': // fraction of second (number) 514 builder.appendFractionOfSecond(tokenLen, tokenLen); 515 break; 516 case 'e': // day of week (number) 517 builder.appendDayOfWeek(tokenLen); 518 break; 519 case 'E': // dayOfWeek (text) 520 if (tokenLen >= 4) { 521 builder.appendDayOfWeekText(); 522 } else { 523 builder.appendDayOfWeekShortText(); 524 } 525 break; 526 case 'D': // day of year (number) 527 builder.appendDayOfYear(tokenLen); 528 break; 529 case 'w': // week of weekyear (number) 530 builder.appendWeekOfWeekyear(tokenLen); 531 break; 532 case 'z': // time zone (text) 533 if (tokenLen >= 4) { 534 builder.appendTimeZoneName(); 535 } else { 536 builder.appendTimeZoneShortName(null); 537 } 538 break; 539 case 'Z': // time zone offset 540 if (tokenLen == 1) { 541 builder.appendTimeZoneOffset(null, "Z", false, 2, 2); 542 } else if (tokenLen == 2) { 543 builder.appendTimeZoneOffset(null, "Z", true, 2, 2); 544 } else { 545 builder.appendTimeZoneId(); 546 } 547 break; 548 case '\'': // literal text 549 String sub = token.substring(1); 550 if (sub.length() == 1) { 551 builder.appendLiteral(sub.charAt(0)); 552 } else { 553 // Create copy of sub since otherwise the temporary quoted 554 // string would still be referenced internally. 555 builder.appendLiteral(new String(sub)); 556 } 557 break; 558 default: 559 throw new IllegalArgumentException 560 ("Illegal pattern component: " + token); 561 } 562 } 563 } 564 565 /** 566 * Parses an individual token. 567 * 568 * @param pattern the pattern string 569 * @param indexRef a single element array, where the input is the start 570 * location and the output is the location after parsing the token 571 * @return the parsed token 572 */ 573 private static String parseToken(String pattern, int[] indexRef) { 574 StringBuilder buf = new StringBuilder(); 575 576 int i = indexRef[0]; 577 int length = pattern.length(); 578 579 char c = pattern.charAt(i); 580 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { 581 // Scan a run of the same character, which indicates a time 582 // pattern. 583 buf.append(c); 584 585 while (i + 1 < length) { 586 char peek = pattern.charAt(i + 1); 587 if (peek == c) { 588 buf.append(c); 589 i++; 590 } else { 591 break; 592 } 593 } 594 } else { 595 // This will identify token as text. 596 buf.append('\''); 597 598 boolean inLiteral = false; 599 600 for (; i < length; i++) { 601 c = pattern.charAt(i); 602 603 if (c == '\'') { 604 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { 605 // '' is treated as escaped ' 606 i++; 607 buf.append(c); 608 } else { 609 inLiteral = !inLiteral; 610 } 611 } else if (!inLiteral && 612 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { 613 i--; 614 break; 615 } else { 616 buf.append(c); 617 } 618 } 619 } 620 621 indexRef[0] = i; 622 return buf.toString(); 623 } 624 625 /** 626 * Returns true if token should be parsed as a numeric field. 627 * 628 * @param token the token to parse 629 * @return true if numeric field 630 */ 631 private static boolean isNumericToken(String token) { 632 int tokenLen = token.length(); 633 if (tokenLen > 0) { 634 char c = token.charAt(0); 635 switch (c) { 636 case 'c': // century (number) 637 case 'C': // century of era (number) 638 case 'x': // weekyear (number) 639 case 'y': // year (number) 640 case 'Y': // year of era (number) 641 case 'd': // day of month (number) 642 case 'h': // hour of day (number, 1..12) 643 case 'H': // hour of day (number, 0..23) 644 case 'm': // minute of hour (number) 645 case 's': // second of minute (number) 646 case 'S': // fraction of second (number) 647 case 'e': // day of week (number) 648 case 'D': // day of year (number) 649 case 'F': // day of week in month (number) 650 case 'w': // week of year (number) 651 case 'W': // week of month (number) 652 case 'k': // hour of day (1..24) 653 case 'K': // hour of day (0..11) 654 return true; 655 case 'M': // month of year (text and number) 656 if (tokenLen <= 2) { 657 return true; 658 } 659 } 660 } 661 662 return false; 663 } 664 665 //----------------------------------------------------------------------- 666 /** 667 * Select a format from a custom pattern. 668 * 669 * @param pattern pattern specification 670 * @throws IllegalArgumentException if the pattern is invalid 671 * @see #appendPatternTo 672 */ 673 private static DateTimeFormatter createFormatterForPattern(String pattern) { 674 if (pattern == null || pattern.length() == 0) { 675 throw new IllegalArgumentException("Invalid pattern specification"); 676 } 677 DateTimeFormatter formatter = null; 678 synchronized (cPatternedCache) { 679 formatter = cPatternedCache.get(pattern); 680 if (formatter == null) { 681 DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); 682 parsePatternTo(builder, pattern); 683 formatter = builder.toFormatter(); 684 685 cPatternedCache.put(pattern, formatter); 686 } 687 } 688 return formatter; 689 } 690 691 /** 692 * Select a format from a two character style pattern. The first character 693 * is the date style, and the second character is the time style. Specify a 694 * character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' 695 * for full. A date or time may be ommitted by specifying a style character '-'. 696 * 697 * @param style two characters from the set {"S", "M", "L", "F", "-"} 698 * @throws IllegalArgumentException if the style is invalid 699 */ 700 private static DateTimeFormatter createFormatterForStyle(String style) { 701 if (style == null || style.length() != 2) { 702 throw new IllegalArgumentException("Invalid style specification: " + style); 703 } 704 int dateStyle = selectStyle(style.charAt(0)); 705 int timeStyle = selectStyle(style.charAt(1)); 706 if (dateStyle == NONE && timeStyle == NONE) { 707 throw new IllegalArgumentException("Style '--' is invalid"); 708 } 709 return createFormatterForStyleIndex(dateStyle, timeStyle); 710 } 711 712 /** 713 * Gets the formatter for the specified style. 714 * 715 * @param dateStyle the date style 716 * @param timeStyle the time style 717 * @return the formatter 718 */ 719 private static DateTimeFormatter createFormatterForStyleIndex(int dateStyle, int timeStyle) { 720 int index = ((dateStyle << 2) + dateStyle) + timeStyle; 721 DateTimeFormatter f = null; 722 synchronized (cStyleCache) { 723 f = cStyleCache[index]; 724 if (f == null) { 725 int type = DATETIME; 726 if (dateStyle == NONE) { 727 type = TIME; 728 } else if (timeStyle == NONE) { 729 type = DATE; 730 } 731 StyleFormatter llf = new StyleFormatter( 732 dateStyle, timeStyle, type); 733 f = new DateTimeFormatter(llf, llf); 734 cStyleCache[index] = f; 735 } 736 } 737 return f; 738 } 739 740 /** 741 * Gets the JDK style code from the Joda code. 742 * 743 * @param ch the Joda style code 744 * @return the JDK style code 745 */ 746 private static int selectStyle(char ch) { 747 switch (ch) { 748 case 'S': 749 return SHORT; 750 case 'M': 751 return MEDIUM; 752 case 'L': 753 return LONG; 754 case 'F': 755 return FULL; 756 case '-': 757 return NONE; 758 default: 759 throw new IllegalArgumentException("Invalid style character: " + ch); 760 } 761 } 762 763 //----------------------------------------------------------------------- 764 static class StyleFormatter 765 implements DateTimePrinter, DateTimeParser { 766 767 private static final Map<String, DateTimeFormatter> cCache = new HashMap<String, DateTimeFormatter>(); // manual sync 768 769 private final int iDateStyle; 770 private final int iTimeStyle; 771 private final int iType; 772 773 StyleFormatter(int dateStyle, int timeStyle, int type) { 774 super(); 775 iDateStyle = dateStyle; 776 iTimeStyle = timeStyle; 777 iType = type; 778 } 779 780 public int estimatePrintedLength() { 781 return 40; // guess 782 } 783 784 public void printTo( 785 StringBuffer buf, long instant, Chronology chrono, 786 int displayOffset, DateTimeZone displayZone, Locale locale) { 787 DateTimePrinter p = getFormatter(locale).getPrinter(); 788 p.printTo(buf, instant, chrono, displayOffset, displayZone, locale); 789 } 790 791 public void printTo( 792 Writer out, long instant, Chronology chrono, 793 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 794 DateTimePrinter p = getFormatter(locale).getPrinter(); 795 p.printTo(out, instant, chrono, displayOffset, displayZone, locale); 796 } 797 798 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 799 DateTimePrinter p = getFormatter(locale).getPrinter(); 800 p.printTo(buf, partial, locale); 801 } 802 803 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 804 DateTimePrinter p = getFormatter(locale).getPrinter(); 805 p.printTo(out, partial, locale); 806 } 807 808 public int estimateParsedLength() { 809 return 40; // guess 810 } 811 812 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 813 DateTimeParser p = getFormatter(bucket.getLocale()).getParser(); 814 return p.parseInto(bucket, text, position); 815 } 816 817 private DateTimeFormatter getFormatter(Locale locale) { 818 locale = (locale == null ? Locale.getDefault() : locale); 819 String key = Integer.toString(iType + (iDateStyle << 4) + (iTimeStyle << 8)) + locale.toString(); 820 DateTimeFormatter f = null; 821 synchronized (cCache) { 822 f = cCache.get(key); 823 if (f == null) { 824 String pattern = getPattern(locale); 825 f = DateTimeFormat.forPattern(pattern); 826 cCache.put(key, f); 827 } 828 } 829 return f; 830 } 831 832 String getPattern(Locale locale) { 833 DateFormat f = null; 834 switch (iType) { 835 case DATE: 836 f = DateFormat.getDateInstance(iDateStyle, locale); 837 break; 838 case TIME: 839 f = DateFormat.getTimeInstance(iTimeStyle, locale); 840 break; 841 case DATETIME: 842 f = DateFormat.getDateTimeInstance(iDateStyle, iTimeStyle, locale); 843 break; 844 } 845 if (f instanceof SimpleDateFormat == false) { 846 throw new IllegalArgumentException("No datetime pattern for locale: " + locale); 847 } 848 return ((SimpleDateFormat) f).toPattern(); 849 } 850 } 851 852 }