EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.format]

COVERAGE SUMMARY FOR SOURCE FILE [DateTimeFormat.java]

nameclass, %method, %block, %line, %
DateTimeFormat.java100% (2/2)83%  (30/36)91%  (823/901)92%  (214.7/234)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class DateTimeFormat$StyleFormatter100% (1/1)64%  (7/11)71%  (134/189)73%  (31.4/43)
estimateParsedLength (): int 0%   (0/1)0%   (0/2)0%   (0/1)
printTo (StringBuffer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/11)0%   (0/3)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/11)0%   (0/3)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/14)0%   (0/3)
getPattern (Locale): String 100% (1/1)72%  (31/43)90%  (9/10)
getFormatter (Locale): DateTimeFormatter 100% (1/1)92%  (58/63)94%  (10.4/11)
<static initializer> 100% (1/1)100% (5/5)100% (1/1)
DateTimeFormat$StyleFormatter (int, int, int): void 100% (1/1)100% (12/12)100% (5/5)
estimatePrintedLength (): int 100% (1/1)100% (2/2)100% (1/1)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)100% (12/12)100% (2/2)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)100% (14/14)100% (3/3)
     
class DateTimeFormat100% (1/1)92%  (23/25)97%  (689/712)96%  (183.4/191)
appendPatternTo (DateTimeFormatterBuilder, String): void 0%   (0/1)0%   (0/4)0%   (0/2)
patternForStyle (String, Locale): String 0%   (0/1)0%   (0/13)0%   (0/4)
createFormatterForStyleIndex (int, int): DateTimeFormatter 100% (1/1)92%  (55/60)96%  (14.4/15)
parsePatternTo (DateTimeFormatterBuilder, String): void 100% (1/1)100% (312/313)99%  (90/91)
<static initializer> 100% (1/1)100% (9/9)100% (2/2)
DateTimeFormat (): void 100% (1/1)100% (3/3)100% (2/2)
createFormatterForPattern (String): DateTimeFormatter 100% (1/1)100% (48/48)100% (12/12)
createFormatterForStyle (String): DateTimeFormatter 100% (1/1)100% (43/43)100% (7/7)
forPattern (String): DateTimeFormatter 100% (1/1)100% (3/3)100% (1/1)
forStyle (String): DateTimeFormatter 100% (1/1)100% (3/3)100% (1/1)
fullDate (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
fullDateTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
fullTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
isNumericToken (String): boolean 100% (1/1)100% (20/20)100% (8/8)
longDate (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
longDateTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
longTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
mediumDate (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
mediumDateTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
mediumTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
parseToken (String, int []): String 100% (1/1)100% (121/121)100% (27/27)
selectStyle (char): int 100% (1/1)100% (24/24)100% (7/7)
shortDate (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
shortDateTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)
shortTime (): DateTimeFormatter 100% (1/1)100% (4/4)100% (1/1)

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

[all classes][org.joda.time.format]
EMMA 2.0.5312 (C) Vladimir Roubtsov