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();
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 StringBuffer buf = new StringBuffer();
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 }