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 (>=0) number 20
61 * Y year of era (>=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