1 /*
2  *  Copyright 2001-2014 Stephen Colebourne
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */

16 package org.joda.time.format;
17
18 import java.io.IOException;
19 import java.text.DateFormat;
20 import java.text.SimpleDateFormat;
21 import java.util.Locale;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.atomic.AtomicReferenceArray;
24
25 import org.joda.time.Chronology;
26 import org.joda.time.DateTime;
27 import org.joda.time.DateTimeZone;
28 import org.joda.time.ReadablePartial;
29
30 /**
31  * Factory that creates instances of DateTimeFormatter from patterns and styles.
32  * <p>
33  * Datetime formatting is performed by the {@link DateTimeFormatter} class.
34  * Three classes provide factory methods to create formatters, and this is one.
35  * The others are {@link ISODateTimeFormat} and {@link DateTimeFormatterBuilder}.
36  * <p>
37  * This class provides two types of factory:
38  * <ul>
39  * <li>{@link #forPattern(String) Pattern} provides a DateTimeFormatter based on
40  * a pattern string that is mostly compatible with the JDK date patterns.
41  * <li>{@link #forStyle(String) Style} provides a DateTimeFormatter based on a
42  * two character style, representing short, medium, long and full.
43  * </ul>
44  * <p>
45  * For example, to use a patterm:
46  * <pre>
47  * DateTime dt = new DateTime();
48  * DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM, yyyy");
49  * String str = fmt.print(dt);
50  * </pre>
51  *
52  * The pattern syntax is mostly compatible with java.text.SimpleDateFormat -
53  * time zone names cannot be parsed and a few more symbols are supported.
54  * All ASCII letters are reserved as pattern letters, which are defined as follows:
55  * <blockquote>
56  * <pre>
57  * Symbol  Meaning                      Presentation  Examples
58  * ------  -------                      ------------  -------
59  * G       era                          text          AD
60  * C       century of era (&gt;=0)         number        20
61  * Y       year of era (&gt;=0)            year          1996
62  *
63  * x       weekyear                     year          1996
64  * w       week of weekyear             number        27
65  * e       day of week                  number        2
66  * E       day of week                  text          Tuesday; Tue
67  *
68  * y       year                         year          1996
69  * D       day of year                  number        189
70  * M       month of year                month         July; Jul; 07
71  * d       day of month                 number        10
72  *
73  * a       halfday of day               text          PM
74  * K       hour of halfday (0~11)       number        0
75  * h       clockhour of halfday (1~12)  number        12
76  *
77  * H       hour of day (0~23)           number        0
78  * k       clockhour of day (1~24)      number        24
79  * m       minute of hour               number        30
80  * s       second of minute             number        55
81  * S       fraction of second           millis        978
82  *
83  * z       time zone                    text          Pacific Standard Time; PST
84  * Z       time zone offset/id          zone          -0800; -08:00; America/Los_Angeles
85  *
86  * '       escape for text              delimiter
87  * ''      single quote                 literal       '
88  * </pre>
89  * </blockquote>
90  * The count of pattern letters determine the format.
91  * <p>
92  * <strong>Text</strong>: If the number of pattern letters is 4 or more,
93  * the full form is used; otherwise a short or abbreviated form is used if
94  * available.
95  * <p>
96  * <strong>Number</strong>: The minimum number of digits.
97  * Shorter numbers are zero-padded to this amount.
98  * When parsing, any number of digits are accepted.
99  * <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>Millis</strong>: The exact number of fractional digits.
108  * If more millisecond digits are available then specified the number will be truncated,
109  * if there are fewer than specified then the number will be zero-padded to the right.
110  * When parsing, only the exact number of digits are accepted.
111  * <p>
112  * <strong>Zone</strong>: 'Z' outputs offset without a colon, 'ZZ' outputs
113  * the offset with a colon, 'ZZZ' or more outputs the zone id.
114  * <p>
115  * <strong>Zone names</strong>: Time zone names ('z') cannot be parsed.
116  * <p>
117  * Any characters in the pattern that are not in the ranges of ['a'..'z']
118  * and ['A'..'Z'] will be treated as quoted text. For instance, characters
119  * like ':', '.', ' ', '#' and '?' will appear in the resulting time text
120  * even they are not embraced within single quotes.
121  * <p>
122  * DateTimeFormat is thread-safe and immutable, and the formatters it returns
123  * are as well.
124  *
125  * @author Brian S O'Neill
126  * @author Maxim Zhao
127  * @since 1.0
128  * @see ISODateTimeFormat
129  * @see DateTimeFormatterBuilder
130  */

131 public class DateTimeFormat {
132
133     /** Style constant for FULL. */
134     static final int FULL = 0;  // DateFormat.FULL
135     /** Style constant for LONG. */
136     static final int LONG = 1;  // DateFormat.LONG
137     /** Style constant for MEDIUM. */
138     static final int MEDIUM = 2;  // DateFormat.MEDIUM
139     /** Style constant for SHORT. */
140     static final int SHORT = 3;  // DateFormat.SHORT
141     /** Style constant for NONE. */
142     static final int NONE = 4;
143
144     /** Type constant for DATE only. */
145     static final int DATE = 0;
146     /** Type constant for TIME only. */
147     static final int TIME = 1;
148     /** Type constant for DATETIME. */
149     static final int DATETIME = 2;
150
151     /** Maximum size of the pattern cache. */
152     private static final int PATTERN_CACHE_SIZE = 500;
153     /** Maps patterns to formatters, patterns don't vary by locale. Size capped at PATTERN_CACHE_SIZE*/
154     private static final ConcurrentHashMap<String, DateTimeFormatter> cPatternCache = new ConcurrentHashMap<String, DateTimeFormatter>();
155     /** Maps patterns to formatters, patterns don't vary by locale. */
156     private static final AtomicReferenceArray<DateTimeFormatter> cStyleCache = new AtomicReferenceArray<DateTimeFormatter>(25);
157
158     //-----------------------------------------------------------------------
159     /**
160      * Factory to create a formatter from a pattern string.
161      * The pattern string is described above in the class level javadoc.
162      * It is very similar to SimpleDateFormat patterns.
163      * <p>
164      * The format may contain locale specific output, and this will change as
165      * you change the locale of the formatter.
166      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
167      * For example:
168      * <pre>
169      * DateTimeFormat.forPattern(pattern).withLocale(Locale.FRANCE).print(dt);
170      * </pre>
171      *
172      * @param pattern  pattern specification
173      * @return the formatter
174      * @throws IllegalArgumentException if the pattern is invalid
175      */

176     public static DateTimeFormatter forPattern(String pattern) {
177         return createFormatterForPattern(pattern);
178     }
179
180     /**
181      * Factory to create a format from a two character style pattern.
182      * <p>
183      * The first character is the date style, and the second character is the
184      * time style. Specify a character of 'S' for short style, 'M' for medium,
185      * 'L' for long, and 'F' for full.
186      * A date or time may be ommitted by specifying a style character '-'.
187      * <p>
188      * The returned formatter will dynamically adjust to the locale that
189      * the print/parse takes place in. Thus you just call
190      * {@link DateTimeFormatter#withLocale(Locale)} and the Short/Medium/Long/Full
191      * style for that locale will be output. For example:
192      * <pre>
193      * DateTimeFormat.forStyle(style).withLocale(Locale.FRANCE).print(dt);
194      * </pre>
195      *
196      * @param style  two characters from the set {"S""M""L""F""-"}
197      * @return the formatter
198      * @throws IllegalArgumentException if the style is invalid
199      */

200     public static DateTimeFormatter forStyle(String style) {
201         return createFormatterForStyle(style);
202     }
203
204     /**
205      * Returns the pattern used by a particular style and locale.
206      * <p>
207      * The first character is the date style, and the second character is the
208      * time style. Specify a character of 'S' for short style, 'M' for medium,
209      * 'L' for long, and 'F' for full.
210      * A date or time may be ommitted by specifying a style character '-'.
211      *
212      * @param style  two characters from the set {"S""M""L""F""-"}
213      * @param locale  locale to use, null means default
214      * @return the formatter
215      * @throws IllegalArgumentException if the style is invalid
216      * @since 1.3
217      */

218     public static String patternForStyle(String style, Locale locale) {
219         DateTimeFormatter formatter = createFormatterForStyle(style);
220         if (locale == null) {
221             locale = Locale.getDefault();
222         }
223         // Not pretty, but it works.
224         return ((StyleFormatter) formatter.getPrinter0()).getPattern(locale);
225     }
226
227     //-----------------------------------------------------------------------
228     /**
229      * Creates a format that outputs a short date format.
230      * <p>
231      * The format will change as you change the locale of the formatter.
232      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
233      * 
234      * @return the formatter
235      */

236     public static DateTimeFormatter shortDate() {
237         return createFormatterForStyleIndex(SHORT, NONE);
238     }
239
240     /**
241      * Creates a format that outputs a short time format.
242      * <p>
243      * The format will change as you change the locale of the formatter.
244      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
245      * 
246      * @return the formatter
247      */

248     public static DateTimeFormatter shortTime() {
249         return createFormatterForStyleIndex(NONE, SHORT);
250     }
251
252     /**
253      * Creates a format that outputs a short datetime format.
254      * <p>
255      * The format will change as you change the locale of the formatter.
256      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
257      * 
258      * @return the formatter
259      */

260     public static DateTimeFormatter shortDateTime() {
261         return createFormatterForStyleIndex(SHORT, SHORT);
262     }
263
264     //-----------------------------------------------------------------------
265     /**
266      * Creates a format that outputs a medium date format.
267      * <p>
268      * The format will change as you change the locale of the formatter.
269      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
270      * 
271      * @return the formatter
272      */

273     public static DateTimeFormatter mediumDate() {
274         return createFormatterForStyleIndex(MEDIUM, NONE);
275     }
276
277     /**
278      * Creates a format that outputs a medium time format.
279      * <p>
280      * The format will change as you change the locale of the formatter.
281      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
282      * 
283      * @return the formatter
284      */

285     public static DateTimeFormatter mediumTime() {
286         return createFormatterForStyleIndex(NONE, MEDIUM);
287     }
288
289     /**
290      * Creates a format that outputs a medium datetime format.
291      * <p>
292      * The format will change as you change the locale of the formatter.
293      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
294      * 
295      * @return the formatter
296      */

297     public static DateTimeFormatter mediumDateTime() {
298         return createFormatterForStyleIndex(MEDIUM, MEDIUM);
299     }
300
301     //-----------------------------------------------------------------------
302     /**
303      * Creates a format that outputs a long date format.
304      * <p>
305      * The format will change as you change the locale of the formatter.
306      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
307      * 
308      * @return the formatter
309      */

310     public static DateTimeFormatter longDate() {
311         return createFormatterForStyleIndex(LONG, NONE);
312     }
313
314     /**
315      * Creates a format that outputs a long time format.
316      * <p>
317      * The format will change as you change the locale of the formatter.
318      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
319      * 
320      * @return the formatter
321      */

322     public static DateTimeFormatter longTime() {
323         return createFormatterForStyleIndex(NONE, LONG);
324     }
325
326     /**
327      * Creates a format that outputs a long datetime format.
328      * <p>
329      * The format will change as you change the locale of the formatter.
330      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
331      * 
332      * @return the formatter
333      */

334     public static DateTimeFormatter longDateTime() {
335         return createFormatterForStyleIndex(LONG, LONG);
336     }
337
338     //-----------------------------------------------------------------------
339     /**
340      * Creates a format that outputs a full date format.
341      * <p>
342      * The format will change as you change the locale of the formatter.
343      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
344      * 
345      * @return the formatter
346      */

347     public static DateTimeFormatter fullDate() {
348         return createFormatterForStyleIndex(FULL, NONE);
349     }
350
351     /**
352      * Creates a format that outputs a full time format.
353      * <p>
354      * The format will change as you change the locale of the formatter.
355      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
356      * 
357      * @return the formatter
358      */

359     public static DateTimeFormatter fullTime() {
360         return createFormatterForStyleIndex(NONE, FULL);
361     }
362
363     /**
364      * Creates a format that outputs a full datetime format.
365      * <p>
366      * The format will change as you change the locale of the formatter.
367      * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
368      * 
369      * @return the formatter
370      */

371     public static DateTimeFormatter fullDateTime() {
372         return createFormatterForStyleIndex(FULL, FULL);
373     }
374
375     //-----------------------------------------------------------------------
376     /**
377      * Parses the given pattern and appends the rules to the given
378      * DateTimeFormatterBuilder.
379      *
380      * @param pattern  pattern specification
381      * @throws IllegalArgumentException if the pattern is invalid
382      */

383     static void appendPatternTo(DateTimeFormatterBuilder builder, String pattern) {
384         parsePatternTo(builder, pattern);
385     }
386
387     //-----------------------------------------------------------------------
388     /**
389      * Constructor.
390      *
391      * @since 1.1 (previously private)
392      */

393     protected DateTimeFormat() {
394         super();
395     }
396
397     //-----------------------------------------------------------------------
398     /**
399      * Parses the given pattern and appends the rules to the given
400      * DateTimeFormatterBuilder.
401      *
402      * @param pattern  pattern specification
403      * @throws IllegalArgumentException if the pattern is invalid
404      * @see #forPattern
405      */

406     private static void parsePatternTo(DateTimeFormatterBuilder builder, String pattern) {
407         int length = pattern.length();
408         int[] indexRef = new int[1];
409
410         for (int i=0; i<length; i++) {
411             indexRef[0] = i;
412             String token = parseToken(pattern, indexRef);
413             i = indexRef[0];
414
415             int tokenLen = token.length();
416             if (tokenLen == 0) {
417                 break;
418             }
419             char c = token.charAt(0);
420
421             switch (c) {
422             case 'G': // era designator (text)
423                 builder.appendEraText();
424                 break;
425             case 'C': // century of era (number)
426                 builder.appendCenturyOfEra(tokenLen, tokenLen);
427                 break;
428             case 'x': // weekyear (number)
429             case 'y': // year (number)
430             case 'Y': // year of era (number)
431                 if (tokenLen == 2) {
432                     boolean lenientParse = true;
433
434                     // Peek ahead to next token.
435                     if (i + 1 < length) {
436                         indexRef[0]++;
437                         if (isNumericToken(parseToken(pattern, indexRef))) {
438                             // If next token is a number, cannot support
439                             // lenient parse, because it will consume digits
440                             // that it should not.
441                             lenientParse = false;
442                         }
443                         indexRef[0]--;
444                     }
445
446                     // Use pivots which are compatible with SimpleDateFormat.
447                     switch (c) {
448                     case 'x':
449                         builder.appendTwoDigitWeekyear
450                             (new DateTime().getWeekyear() - 30, lenientParse);
451                         break;
452                     case 'y':
453                     case 'Y':
454                     default:
455                         builder.appendTwoDigitYear(new DateTime().getYear() - 30, lenientParse);
456                         break;
457                     }
458                 } else {
459                     // Try to support long year values.
460                     int maxDigits = 9;
461
462                     // Peek ahead to next token.
463                     if (i + 1 < length) {
464                         indexRef[0]++;
465                         if (isNumericToken(parseToken(pattern, indexRef))) {
466                             // If next token is a number, cannot support long years.
467                             maxDigits = tokenLen;
468                         }
469                         indexRef[0]--;
470                     }
471
472                     switch (c) {
473                     case 'x':
474                         builder.appendWeekyear(tokenLen, maxDigits);
475                         break;
476                     case 'y':
477                         builder.appendYear(tokenLen, maxDigits);
478                         break;
479                     case 'Y':
480                         builder.appendYearOfEra(tokenLen, maxDigits);
481                         break;
482                     }
483                 }
484                 break;
485             case 'M': // month of year (text and number)
486                 if (tokenLen >= 3) {
487                     if (tokenLen >= 4) {
488                         builder.appendMonthOfYearText();
489                     } else {
490                         builder.appendMonthOfYearShortText();
491                     }
492                 } else {
493                     builder.appendMonthOfYear(tokenLen);
494                 }
495                 break;
496             case 'd': // day of month (number)
497                 builder.appendDayOfMonth(tokenLen);
498                 break;
499             case 'a': // am/pm marker (text)
500                 builder.appendHalfdayOfDayText();
501                 break;
502             case 'h': // clockhour of halfday (number, 1..12)
503                 builder.appendClockhourOfHalfday(tokenLen);
504                 break;
505             case 'H': // hour of day (number, 0..23)
506                 builder.appendHourOfDay(tokenLen);
507                 break;
508             case 'k': // clockhour of day (1..24)
509                 builder.appendClockhourOfDay(tokenLen);
510                 break;
511             case 'K': // hour of halfday (0..11)
512                 builder.appendHourOfHalfday(tokenLen);
513                 break;
514             case 'm': // minute of hour (number)
515                 builder.appendMinuteOfHour(tokenLen);
516                 break;
517             case 's': // second of minute (number)
518                 builder.appendSecondOfMinute(tokenLen);
519                 break;
520             case 'S': // fraction of second (number)
521                 builder.appendFractionOfSecond(tokenLen, tokenLen);
522                 break;
523             case 'e': // day of week (number)
524                 builder.appendDayOfWeek(tokenLen);
525                 break;
526             case 'E': // dayOfWeek (text)
527                 if (tokenLen >= 4) {
528                     builder.appendDayOfWeekText();
529                 } else {
530                     builder.appendDayOfWeekShortText();
531                 }
532                 break;
533             case 'D': // day of year (number)
534                 builder.appendDayOfYear(tokenLen);
535                 break;
536             case 'w': // week of weekyear (number)
537                 builder.appendWeekOfWeekyear(tokenLen);
538                 break;
539             case 'z': // time zone (text)
540                 if (tokenLen >= 4) {
541                     builder.appendTimeZoneName();
542                 } else {
543                     builder.appendTimeZoneShortName(null);
544                 }
545                 break;
546             case 'Z': // time zone offset
547                 if (tokenLen == 1) {
548                     builder.appendTimeZoneOffset(null"Z"false, 2, 2);
549                 } else if (tokenLen == 2) {
550                     builder.appendTimeZoneOffset(null"Z"true, 2, 2);
551                 } else {
552                     builder.appendTimeZoneId();
553                 }
554                 break;
555             case '\'': // literal text
556                 String sub = token.substring(1);
557                 if (sub.length() == 1) {
558                     builder.appendLiteral(sub.charAt(0));
559                 } else {
560                     // Create copy of sub since otherwise the temporary quoted
561                     // string would still be referenced internally.
562                     builder.appendLiteral(new String(sub));
563                 }
564                 break;
565             default:
566                 throw new IllegalArgumentException
567                     ("Illegal pattern component: " + token);
568             }
569         }
570     }
571
572     /**
573      * Parses an individual token.
574      * 
575      * @param pattern  the pattern string
576      * @param indexRef  a single element array, where the input is the start
577      *  location and the output is the location after parsing the token
578      * @return the parsed token
579      */

580     private static String parseToken(String pattern, int[] indexRef) {
581         StringBuilder buf = new StringBuilder();
582
583         int i = indexRef[0];
584         int length = pattern.length();
585
586         char c = pattern.charAt(i);
587         if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
588             // Scan a run of the same character, which indicates a time
589             // pattern.
590             buf.append(c);
591
592             while (i + 1 < length) {
593                 char peek = pattern.charAt(i + 1);
594                 if (peek == c) {
595                     buf.append(c);
596                     i++;
597                 } else {
598                     break;
599                 }
600             }
601         } else {
602             // This will identify token as text.
603             buf.append('\'');
604
605             boolean inLiteral = false;
606
607             for (; i < length; i++) {
608                 c = pattern.charAt(i);
609                 
610                 if (c == '\'') {
611                     if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
612                         // '' is treated as escaped '
613                         i++;
614                         buf.append(c);
615                     } else {
616                         inLiteral = !inLiteral;
617                     }
618                 } else if (!inLiteral &&
619                            (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
620                     i--;
621                     break;
622                 } else {
623                     buf.append(c);
624                 }
625             }
626         }
627
628         indexRef[0] = i;
629         return buf.toString();
630     }
631
632     /**
633      * Returns true if token should be parsed as a numeric field.
634      * 
635      * @param token  the token to parse
636      * @return true if numeric field
637      */

638     private static boolean isNumericToken(String token) {
639         int tokenLen = token.length();
640         if (tokenLen > 0) {
641             char c = token.charAt(0);
642             switch (c) {
643             case 'c': // century (number)
644             case 'C': // century of era (number)
645             case 'x': // weekyear (number)
646             case 'y': // year (number)
647             case 'Y': // year of era (number)
648             case 'd': // day of month (number)
649             case 'h': // hour of day (number, 1..12)
650             case 'H': // hour of day (number, 0..23)
651             case 'm': // minute of hour (number)
652             case 's': // second of minute (number)
653             case 'S': // fraction of second (number)
654             case 'e': // day of week (number)
655             case 'D': // day of year (number)
656             case 'F': // day of week in month (number)
657             case 'w': // week of year (number)
658             case 'W': // week of month (number)
659             case 'k': // hour of day (1..24)
660             case 'K': // hour of day (0..11)
661                 return true;
662             case 'M': // month of year (text and number)
663                 if (tokenLen <= 2) {
664                     return true;
665                 }
666             }
667         }
668             
669         return false;
670     }
671
672     //-----------------------------------------------------------------------
673     /**
674      * Select a format from a custom pattern.
675      *
676      * @param pattern  pattern specification
677      * @throws IllegalArgumentException if the pattern is invalid
678      * @see #appendPatternTo
679      */

680     private static DateTimeFormatter createFormatterForPattern(String pattern) {
681         if (pattern == null || pattern.length() == 0) {
682             throw new IllegalArgumentException("Invalid pattern specification");
683         }
684         DateTimeFormatter formatter = cPatternCache.get(pattern);
685         if (formatter == null) {
686             DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
687             parsePatternTo(builder, pattern);
688             formatter = builder.toFormatter();
689             if (cPatternCache.size() < PATTERN_CACHE_SIZE) {
690                 // the size check is not locked against concurrent access,
691                 // but is accepted to be slightly off in contention scenarios.
692                 DateTimeFormatter oldFormatter = cPatternCache.putIfAbsent(pattern, formatter);
693                 if (oldFormatter != null) {
694                     formatter = oldFormatter;
695                 }
696             }
697         }
698         return formatter;
699     }
700
701     /**
702      * Select a format from a two character style pattern. The first character
703      * is the date style, and the second character is the time style. Specify a
704      * character of 'S' for short style, 'M' for medium, 'L' for long, and 'F'
705      * for full. A date or time may be ommitted by specifying a style character '-'.
706      *
707      * @param style  two characters from the set {"S""M""L""F""-"}
708      * @throws IllegalArgumentException if the style is invalid
709      */

710     private static DateTimeFormatter createFormatterForStyle(String style) {
711         if (style == null || style.length() != 2) {
712             throw new IllegalArgumentException("Invalid style specification: " + style);
713         }
714         int dateStyle = selectStyle(style.charAt(0));
715         int timeStyle = selectStyle(style.charAt(1));
716         if (dateStyle == NONE && timeStyle == NONE) {
717             throw new IllegalArgumentException("Style '--' is invalid");
718         }
719         return createFormatterForStyleIndex(dateStyle, timeStyle);
720     }
721
722     /**
723      * Gets the formatter for the specified style.
724      * 
725      * @param dateStyle  the date style
726      * @param timeStyle  the time style
727      * @return the formatter
728      */

729     private static DateTimeFormatter createFormatterForStyleIndex(int dateStyle, int timeStyle) {
730         int index = ((dateStyle << 2) + dateStyle) + timeStyle;  // (dateStyle * 5 + timeStyle);
731         // Should never happen but do a double check...
732         if (index >= cStyleCache.length()) {
733             return createDateTimeFormatter(dateStyle, timeStyle);
734         }
735         DateTimeFormatter f = cStyleCache.get(index);
736         if (f == null) {
737             f = createDateTimeFormatter(dateStyle, timeStyle);
738             if (cStyleCache.compareAndSet(index, null, f) == false) {
739                 f = cStyleCache.get(index);
740             }
741         }
742         return f;
743     }
744
745     /**
746      * Creates a formatter for the specified style.
747      * 
748      * @param dateStyle  the date style
749      * @param timeStyle  the time style
750      * @return the formatter
751      */

752     private static DateTimeFormatter createDateTimeFormatter(int dateStyle, int timeStyle){
753         int type = DATETIME;
754         if (dateStyle == NONE) {
755             type = TIME;
756         } else if (timeStyle == NONE) {
757             type = DATE;
758         }
759         StyleFormatter llf = new StyleFormatter(dateStyle, timeStyle, type);
760         return new DateTimeFormatter(llf, llf);
761     }
762
763     /**
764      * Gets the JDK style code from the Joda code.
765      * 
766      * @param ch  the Joda style code
767      * @return the JDK style code
768      */

769     private static int selectStyle(char ch) {
770         switch (ch) {
771         case 'S':
772             return SHORT;
773         case 'M':
774             return MEDIUM;
775         case 'L':
776             return LONG;
777         case 'F':
778             return FULL;
779         case '-':
780             return NONE;
781         default:
782             throw new IllegalArgumentException("Invalid style character: " + ch);
783         }
784     }
785
786     //-----------------------------------------------------------------------
787     static class StyleFormatter
788             implements InternalPrinter, InternalParser {
789
790         private static final ConcurrentHashMap<StyleFormatterCacheKey, DateTimeFormatter> cCache = new ConcurrentHashMap<StyleFormatterCacheKey, DateTimeFormatter>();
791         
792         private final int iDateStyle;
793         private final int iTimeStyle;
794         private final int iType;
795
796         StyleFormatter(int dateStyle, int timeStyle, int type) {
797             super();
798             iDateStyle = dateStyle;
799             iTimeStyle = timeStyle;
800             iType = type;
801         }
802
803         public int estimatePrintedLength() {
804             return 40;  // guess
805         }
806
807         public void printTo(
808                 Appendable appenadble, long instant, Chronology chrono,
809                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
810             InternalPrinter p = getFormatter(locale).getPrinter0();
811             p.printTo(appenadble, instant, chrono, displayOffset, displayZone, locale);
812         }
813
814         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
815             InternalPrinter p = getFormatter(locale).getPrinter0();
816             p.printTo(appendable, partial, locale);
817         }
818
819         public int estimateParsedLength() {
820             return 40;  // guess
821         }
822
823         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
824             InternalParser p = getFormatter(bucket.getLocale()).getParser0();
825             return p.parseInto(bucket, text, position);
826         }
827
828         private DateTimeFormatter getFormatter(Locale locale) {
829             locale = (locale == null ? Locale.getDefault() : locale);
830             StyleFormatterCacheKey key = new StyleFormatterCacheKey(iType, iDateStyle, iTimeStyle, locale);
831             DateTimeFormatter f = cCache.get(key);
832             if (f == null) {
833                 f = DateTimeFormat.forPattern(getPattern(locale));
834                 DateTimeFormatter oldFormatter = cCache.putIfAbsent(key, f);
835                 if (oldFormatter != null) {
836                     f = oldFormatter;
837                 }
838             }
839             return f;
840         }
841
842         String getPattern(Locale locale) {
843             DateFormat f = null;
844             switch (iType) {
845                 case DATE:
846                     f = DateFormat.getDateInstance(iDateStyle, locale);
847                     break;
848                 case TIME:
849                     f = DateFormat.getTimeInstance(iTimeStyle, locale);
850                     break;
851                 case DATETIME:
852                     f = DateFormat.getDateTimeInstance(iDateStyle, iTimeStyle, locale);
853                     break;
854             }
855             if (f instanceof SimpleDateFormat == false) {
856                 throw new IllegalArgumentException("No datetime pattern for locale: " + locale);
857             }
858             return ((SimpleDateFormat) f).toPattern();
859         }
860     }
861
862     static class StyleFormatterCacheKey {
863         private final int combinedTypeAndStyle;
864         private final Locale locale;
865
866         public StyleFormatterCacheKey(int iType, int iDateStyle,
867                 int iTimeStyle, Locale locale) {
868             this.locale = locale;
869             // keeping old key generation logic of shifting type and style
870             this.combinedTypeAndStyle = iType + (iDateStyle << 4) + (iTimeStyle << 8);
871         }
872
873         @Override
874         public int hashCode() {
875             final int prime = 31;
876             int result = 1;
877             result = prime * result + combinedTypeAndStyle;
878             result = prime * result + ((locale == null) ? 0 : locale.hashCode());
879             return result;
880         }
881
882         @Override
883         public boolean equals(Object obj) {
884             if (this == obj) {
885                 return true;
886             }
887             if (obj == null) {
888                 return false;
889             }
890             if (!(obj instanceof StyleFormatterCacheKey)) {
891                 return false;
892             }
893             StyleFormatterCacheKey other = (StyleFormatterCacheKey) obj;
894             if (combinedTypeAndStyle != other.combinedTypeAndStyle) {
895                 return false;
896             }
897             if (locale == null) {
898                 if (other.locale != null) {
899                     return false;
900                 }
901             } else if (!locale.equals(other.locale)) {
902                 return false;
903             }
904             return true;
905         }
906     }
907 }
908