001: /*
002: * Copyright 2001-2005 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 cPatternedCache = new HashMap(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())
218: .getPattern(locale);
219: }
220:
221: //-----------------------------------------------------------------------
222: /**
223: * Creates a format that outputs a short date format.
224: * <p>
225: * The format will change as you change the locale of the formatter.
226: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
227: *
228: * @return the formatter
229: */
230: public static DateTimeFormatter shortDate() {
231: return createFormatterForStyleIndex(SHORT, NONE);
232: }
233:
234: /**
235: * Creates a format that outputs a short time format.
236: * <p>
237: * The format will change as you change the locale of the formatter.
238: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
239: *
240: * @return the formatter
241: */
242: public static DateTimeFormatter shortTime() {
243: return createFormatterForStyleIndex(NONE, SHORT);
244: }
245:
246: /**
247: * Creates a format that outputs a short datetime format.
248: * <p>
249: * The format will change as you change the locale of the formatter.
250: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
251: *
252: * @return the formatter
253: */
254: public static DateTimeFormatter shortDateTime() {
255: return createFormatterForStyleIndex(SHORT, SHORT);
256: }
257:
258: //-----------------------------------------------------------------------
259: /**
260: * Creates a format that outputs a medium date format.
261: * <p>
262: * The format will change as you change the locale of the formatter.
263: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
264: *
265: * @return the formatter
266: */
267: public static DateTimeFormatter mediumDate() {
268: return createFormatterForStyleIndex(MEDIUM, NONE);
269: }
270:
271: /**
272: * Creates a format that outputs a medium time format.
273: * <p>
274: * The format will change as you change the locale of the formatter.
275: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
276: *
277: * @return the formatter
278: */
279: public static DateTimeFormatter mediumTime() {
280: return createFormatterForStyleIndex(NONE, MEDIUM);
281: }
282:
283: /**
284: * Creates a format that outputs a medium datetime format.
285: * <p>
286: * The format will change as you change the locale of the formatter.
287: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
288: *
289: * @return the formatter
290: */
291: public static DateTimeFormatter mediumDateTime() {
292: return createFormatterForStyleIndex(MEDIUM, MEDIUM);
293: }
294:
295: //-----------------------------------------------------------------------
296: /**
297: * Creates a format that outputs a long date format.
298: * <p>
299: * The format will change as you change the locale of the formatter.
300: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
301: *
302: * @return the formatter
303: */
304: public static DateTimeFormatter longDate() {
305: return createFormatterForStyleIndex(LONG, NONE);
306: }
307:
308: /**
309: * Creates a format that outputs a long time format.
310: * <p>
311: * The format will change as you change the locale of the formatter.
312: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
313: *
314: * @return the formatter
315: */
316: public static DateTimeFormatter longTime() {
317: return createFormatterForStyleIndex(NONE, LONG);
318: }
319:
320: /**
321: * Creates a format that outputs a long datetime format.
322: * <p>
323: * The format will change as you change the locale of the formatter.
324: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
325: *
326: * @return the formatter
327: */
328: public static DateTimeFormatter longDateTime() {
329: return createFormatterForStyleIndex(LONG, LONG);
330: }
331:
332: //-----------------------------------------------------------------------
333: /**
334: * Creates a format that outputs a full date format.
335: * <p>
336: * The format will change as you change the locale of the formatter.
337: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
338: *
339: * @return the formatter
340: */
341: public static DateTimeFormatter fullDate() {
342: return createFormatterForStyleIndex(FULL, NONE);
343: }
344:
345: /**
346: * Creates a format that outputs a full time format.
347: * <p>
348: * The format will change as you change the locale of the formatter.
349: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
350: *
351: * @return the formatter
352: */
353: public static DateTimeFormatter fullTime() {
354: return createFormatterForStyleIndex(NONE, FULL);
355: }
356:
357: /**
358: * Creates a format that outputs a full datetime format.
359: * <p>
360: * The format will change as you change the locale of the formatter.
361: * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale.
362: *
363: * @return the formatter
364: */
365: public static DateTimeFormatter fullDateTime() {
366: return createFormatterForStyleIndex(FULL, FULL);
367: }
368:
369: //-----------------------------------------------------------------------
370: /**
371: * Parses the given pattern and appends the rules to the given
372: * DateTimeFormatterBuilder.
373: *
374: * @param pattern pattern specification
375: * @throws IllegalArgumentException if the pattern is invalid
376: */
377: static void appendPatternTo(DateTimeFormatterBuilder builder,
378: String pattern) {
379: parsePatternTo(builder, pattern);
380: }
381:
382: //-----------------------------------------------------------------------
383: /**
384: * Constructor.
385: *
386: * @since 1.1 (previously private)
387: */
388: protected DateTimeFormat() {
389: super ();
390: }
391:
392: //-----------------------------------------------------------------------
393: /**
394: * Parses the given pattern and appends the rules to the given
395: * DateTimeFormatterBuilder.
396: *
397: * @param pattern pattern specification
398: * @throws IllegalArgumentException if the pattern is invalid
399: * @see #forPattern
400: */
401: private static void parsePatternTo(
402: DateTimeFormatterBuilder builder, String pattern) {
403: int length = pattern.length();
404: int[] indexRef = new int[1];
405:
406: for (int i = 0; i < length; i++) {
407: indexRef[0] = i;
408: String token = parseToken(pattern, indexRef);
409: i = indexRef[0];
410:
411: int tokenLen = token.length();
412: if (tokenLen == 0) {
413: break;
414: }
415: char c = token.charAt(0);
416:
417: switch (c) {
418: case 'G': // era designator (text)
419: builder.appendEraText();
420: break;
421: case 'C': // century of era (number)
422: builder.appendCenturyOfEra(tokenLen, tokenLen);
423: break;
424: case 'x': // weekyear (number)
425: case 'y': // year (number)
426: case 'Y': // year of era (number)
427: if (tokenLen == 2) {
428: boolean lenientParse = true;
429:
430: // Peek ahead to next token.
431: if (i + 1 < length) {
432: indexRef[0]++;
433: if (isNumericToken(parseToken(pattern, indexRef))) {
434: // If next token is a number, cannot support
435: // lenient parse, because it will consume digits
436: // that it should not.
437: lenientParse = false;
438: }
439: indexRef[0]--;
440: }
441:
442: // Use pivots which are compatible with SimpleDateFormat.
443: switch (c) {
444: case 'x':
445: builder.appendTwoDigitWeekyear(new DateTime()
446: .getWeekyear() - 30, lenientParse);
447: break;
448: case 'y':
449: case 'Y':
450: default:
451: builder.appendTwoDigitYear(new DateTime()
452: .getYear() - 30, lenientParse);
453: break;
454: }
455: } else {
456: // Try to support long year values.
457: int maxDigits = 9;
458:
459: // Peek ahead to next token.
460: if (i + 1 < length) {
461: indexRef[0]++;
462: if (isNumericToken(parseToken(pattern, indexRef))) {
463: // If next token is a number, cannot support long years.
464: maxDigits = tokenLen;
465: }
466: indexRef[0]--;
467: }
468:
469: switch (c) {
470: case 'x':
471: builder.appendWeekyear(tokenLen, maxDigits);
472: break;
473: case 'y':
474: builder.appendYear(tokenLen, maxDigits);
475: break;
476: case 'Y':
477: builder.appendYearOfEra(tokenLen, maxDigits);
478: break;
479: }
480: }
481: break;
482: case 'M': // month of year (text and number)
483: if (tokenLen >= 3) {
484: if (tokenLen >= 4) {
485: builder.appendMonthOfYearText();
486: } else {
487: builder.appendMonthOfYearShortText();
488: }
489: } else {
490: builder.appendMonthOfYear(tokenLen);
491: }
492: break;
493: case 'd': // day of month (number)
494: builder.appendDayOfMonth(tokenLen);
495: break;
496: case 'a': // am/pm marker (text)
497: builder.appendHalfdayOfDayText();
498: break;
499: case 'h': // clockhour of halfday (number, 1..12)
500: builder.appendClockhourOfHalfday(tokenLen);
501: break;
502: case 'H': // hour of day (number, 0..23)
503: builder.appendHourOfDay(tokenLen);
504: break;
505: case 'k': // clockhour of day (1..24)
506: builder.appendClockhourOfDay(tokenLen);
507: break;
508: case 'K': // hour of halfday (0..11)
509: builder.appendHourOfHalfday(tokenLen);
510: break;
511: case 'm': // minute of hour (number)
512: builder.appendMinuteOfHour(tokenLen);
513: break;
514: case 's': // second of minute (number)
515: builder.appendSecondOfMinute(tokenLen);
516: break;
517: case 'S': // fraction of second (number)
518: builder.appendFractionOfSecond(tokenLen, tokenLen);
519: break;
520: case 'e': // day of week (number)
521: builder.appendDayOfWeek(tokenLen);
522: break;
523: case 'E': // dayOfWeek (text)
524: if (tokenLen >= 4) {
525: builder.appendDayOfWeekText();
526: } else {
527: builder.appendDayOfWeekShortText();
528: }
529: break;
530: case 'D': // day of year (number)
531: builder.appendDayOfYear(tokenLen);
532: break;
533: case 'w': // week of weekyear (number)
534: builder.appendWeekOfWeekyear(tokenLen);
535: break;
536: case 'z': // time zone (text)
537: if (tokenLen >= 4) {
538: builder.appendTimeZoneName();
539: } else {
540: builder.appendTimeZoneShortName();
541: }
542: break;
543: case 'Z': // time zone offset
544: if (tokenLen == 1) {
545: builder.appendTimeZoneOffset(null, false, 2, 2);
546: } else if (tokenLen == 2) {
547: builder.appendTimeZoneOffset(null, true, 2, 2);
548: } else {
549: builder.appendTimeZoneId();
550: }
551: break;
552: case '\'': // literal text
553: String sub = token.substring(1);
554: if (sub.length() == 1) {
555: builder.appendLiteral(sub.charAt(0));
556: } else {
557: // Create copy of sub since otherwise the temporary quoted
558: // string would still be referenced internally.
559: builder.appendLiteral(new String(sub));
560: }
561: break;
562: default:
563: throw new IllegalArgumentException(
564: "Illegal pattern component: " + token);
565: }
566: }
567: }
568:
569: /**
570: * Parses an individual token.
571: *
572: * @param pattern the pattern string
573: * @param indexRef a single element array, where the input is the start
574: * location and the output is the location after parsing the token
575: * @return the parsed token
576: */
577: private static String parseToken(String pattern, int[] indexRef) {
578: StringBuffer buf = new StringBuffer();
579:
580: int i = indexRef[0];
581: int length = pattern.length();
582:
583: char c = pattern.charAt(i);
584: if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
585: // Scan a run of the same character, which indicates a time
586: // pattern.
587: buf.append(c);
588:
589: while (i + 1 < length) {
590: char peek = pattern.charAt(i + 1);
591: if (peek == c) {
592: buf.append(c);
593: i++;
594: } else {
595: break;
596: }
597: }
598: } else {
599: // This will identify token as text.
600: buf.append('\'');
601:
602: boolean inLiteral = false;
603:
604: for (; i < length; i++) {
605: c = pattern.charAt(i);
606:
607: if (c == '\'') {
608: if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
609: // '' is treated as escaped '
610: i++;
611: buf.append(c);
612: } else {
613: inLiteral = !inLiteral;
614: }
615: } else if (!inLiteral
616: && (c >= 'A' && c <= 'Z' || c >= 'a'
617: && c <= 'z')) {
618: i--;
619: break;
620: } else {
621: buf.append(c);
622: }
623: }
624: }
625:
626: indexRef[0] = i;
627: return buf.toString();
628: }
629:
630: /**
631: * Returns true if token should be parsed as a numeric field.
632: *
633: * @param token the token to parse
634: * @return true if numeric field
635: */
636: private static boolean isNumericToken(String token) {
637: int tokenLen = token.length();
638: if (tokenLen > 0) {
639: char c = token.charAt(0);
640: switch (c) {
641: case 'c': // century (number)
642: case 'C': // century of era (number)
643: case 'x': // weekyear (number)
644: case 'y': // year (number)
645: case 'Y': // year of era (number)
646: case 'd': // day of month (number)
647: case 'h': // hour of day (number, 1..12)
648: case 'H': // hour of day (number, 0..23)
649: case 'm': // minute of hour (number)
650: case 's': // second of minute (number)
651: case 'S': // fraction of second (number)
652: case 'e': // day of week (number)
653: case 'D': // day of year (number)
654: case 'F': // day of week in month (number)
655: case 'w': // week of year (number)
656: case 'W': // week of month (number)
657: case 'k': // hour of day (1..24)
658: case 'K': // hour of day (0..11)
659: return true;
660: case 'M': // month of year (text and number)
661: if (tokenLen <= 2) {
662: return true;
663: }
664: }
665: }
666:
667: return false;
668: }
669:
670: //-----------------------------------------------------------------------
671: /**
672: * Select a format from a custom pattern.
673: *
674: * @param pattern pattern specification
675: * @throws IllegalArgumentException if the pattern is invalid
676: * @see #appendPatternTo
677: */
678: private static DateTimeFormatter createFormatterForPattern(
679: String pattern) {
680: if (pattern == null || pattern.length() == 0) {
681: throw new IllegalArgumentException(
682: "Invalid pattern specification");
683: }
684: DateTimeFormatter formatter = null;
685: synchronized (cPatternedCache) {
686: formatter = (DateTimeFormatter) cPatternedCache
687: .get(pattern);
688: if (formatter == null) {
689: DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
690: parsePatternTo(builder, pattern);
691: formatter = builder.toFormatter();
692:
693: cPatternedCache.put(pattern, formatter);
694: }
695: }
696: return formatter;
697: }
698:
699: /**
700: * Select a format from a two character style pattern. The first character
701: * is the date style, and the second character is the time style. Specify a
702: * character of 'S' for short style, 'M' for medium, 'L' for long, and 'F'
703: * for full. A date or time may be ommitted by specifying a style character '-'.
704: *
705: * @param style two characters from the set {"S", "M", "L", "F", "-"}
706: * @throws IllegalArgumentException if the style is invalid
707: */
708: private static DateTimeFormatter createFormatterForStyle(
709: String style) {
710: if (style == null || style.length() != 2) {
711: throw new IllegalArgumentException(
712: "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(
730: int dateStyle, int timeStyle) {
731: int index = ((dateStyle << 2) + dateStyle) + timeStyle;
732: DateTimeFormatter f = null;
733: synchronized (cStyleCache) {
734: f = cStyleCache[index];
735: if (f == null) {
736: int type = DATETIME;
737: if (dateStyle == NONE) {
738: type = TIME;
739: } else if (timeStyle == NONE) {
740: type = DATE;
741: }
742: StyleFormatter llf = new StyleFormatter(dateStyle,
743: timeStyle, type);
744: f = new DateTimeFormatter(llf, llf);
745: cStyleCache[index] = f;
746: }
747: }
748: return f;
749: }
750:
751: /**
752: * Gets the JDK style code from the Joda code.
753: *
754: * @param ch the Joda style code
755: * @return the JDK style code
756: */
757: private static int selectStyle(char ch) {
758: switch (ch) {
759: case 'S':
760: return SHORT;
761: case 'M':
762: return MEDIUM;
763: case 'L':
764: return LONG;
765: case 'F':
766: return FULL;
767: case '-':
768: return NONE;
769: default:
770: throw new IllegalArgumentException(
771: "Invalid style character: " + ch);
772: }
773: }
774:
775: //-----------------------------------------------------------------------
776: static class StyleFormatter implements DateTimePrinter,
777: DateTimeParser {
778:
779: private static final Map cCache = new HashMap(); // manual sync
780:
781: private final int iDateStyle;
782: private final int iTimeStyle;
783: private final int iType;
784:
785: StyleFormatter(int dateStyle, int timeStyle, int type) {
786: super ();
787: iDateStyle = dateStyle;
788: iTimeStyle = timeStyle;
789: iType = type;
790: }
791:
792: public int estimatePrintedLength() {
793: return 40; // guess
794: }
795:
796: public void printTo(StringBuffer buf, long instant,
797: Chronology chrono, int displayOffset,
798: DateTimeZone displayZone, Locale locale) {
799: DateTimePrinter p = getFormatter(locale).getPrinter();
800: p.printTo(buf, instant, chrono, displayOffset, displayZone,
801: locale);
802: }
803:
804: public void printTo(Writer out, long instant,
805: Chronology chrono, int displayOffset,
806: DateTimeZone displayZone, Locale locale)
807: throws IOException {
808: DateTimePrinter p = getFormatter(locale).getPrinter();
809: p.printTo(out, instant, chrono, displayOffset, displayZone,
810: locale);
811: }
812:
813: public void printTo(StringBuffer buf, ReadablePartial partial,
814: Locale locale) {
815: DateTimePrinter p = getFormatter(locale).getPrinter();
816: p.printTo(buf, partial, locale);
817: }
818:
819: public void printTo(Writer out, ReadablePartial partial,
820: Locale locale) throws IOException {
821: DateTimePrinter p = getFormatter(locale).getPrinter();
822: p.printTo(out, partial, locale);
823: }
824:
825: public int estimateParsedLength() {
826: return 40; // guess
827: }
828:
829: public int parseInto(DateTimeParserBucket bucket, String text,
830: int position) {
831: DateTimeParser p = getFormatter(bucket.getLocale())
832: .getParser();
833: return p.parseInto(bucket, text, position);
834: }
835:
836: private DateTimeFormatter getFormatter(Locale locale) {
837: locale = (locale == null ? Locale.getDefault() : locale);
838: String key = Integer.toString(iType + (iDateStyle << 4)
839: + (iTimeStyle << 8))
840: + locale.toString();
841: DateTimeFormatter f = null;
842: synchronized (cCache) {
843: f = (DateTimeFormatter) cCache.get(key);
844: if (f == null) {
845: String pattern = getPattern(locale);
846: f = DateTimeFormat.forPattern(pattern);
847: cCache.put(key, f);
848: }
849: }
850: return f;
851: }
852:
853: String getPattern(Locale locale) {
854: DateFormat f = null;
855: switch (iType) {
856: case DATE:
857: f = DateFormat.getDateInstance(iDateStyle, locale);
858: break;
859: case TIME:
860: f = DateFormat.getTimeInstance(iTimeStyle, locale);
861: break;
862: case DATETIME:
863: f = DateFormat.getDateTimeInstance(iDateStyle,
864: iTimeStyle, locale);
865: break;
866: }
867: if (f instanceof SimpleDateFormat == false) {
868: throw new IllegalArgumentException(
869: "No datetime pattern for locale: " + locale);
870: }
871: return ((SimpleDateFormat) f).toPattern();
872: }
873: }
874:
875: }
|