001    /*
002     *  Copyright 2001-2006,2008 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.util.Collection;
019    import java.util.HashSet;
020    import java.util.Set;
021    
022    import org.joda.time.DateTimeFieldType;
023    import org.joda.time.DateTimeZone;
024    
025    /**
026     * Factory that creates instances of DateTimeFormatter for the ISO8601 standard.
027     * <p>
028     * Datetime formatting is performed by the {@link DateTimeFormatter} class.
029     * Three classes provide factory methods to create formatters, and this is one.
030     * The others are {@link DateTimeFormat} and {@link DateTimeFormatterBuilder}.
031     * <p>
032     * ISO8601 is the international standard for data interchange. It defines a
033     * framework, rather than an absolute standard. As a result this provider has a
034     * number of methods that represent common uses of the framework. The most common
035     * formats are {@link #date() date}, {@link #time() time}, and {@link #dateTime() dateTime}.
036     * <p>
037     * For example, to format a date time in ISO format:
038     * <pre>
039     * DateTime dt = new DateTime();
040     * DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
041     * String str = fmt.print(dt);
042     * </pre>
043     * <p>
044     * It is important to understand that these formatters are not linked to
045     * the <code>ISOChronology</code>. These formatters may be used with any
046     * chronology, however there may be certain side effects with more unusual
047     * chronologies. For example, the ISO formatters rely on dayOfWeek being
048     * single digit, dayOfMonth being two digit and dayOfYear being three digit.
049     * A chronology with a ten day week would thus cause issues. However, in
050     * general, it is safe to use these formatters with other chronologies.
051     * <p>
052     * ISODateTimeFormat is thread-safe and immutable, and the formatters it
053     * returns are as well.
054     *
055     * @author Brian S O'Neill
056     * @since 1.0
057     * @see DateTimeFormat
058     * @see DateTimeFormatterBuilder
059     */
060    public class ISODateTimeFormat {
061    
062        //-----------------------------------------------------------------------
063        private static DateTimeFormatter
064            ye,  // year element (yyyy)
065            mye, // monthOfYear element (-MM)
066            dme, // dayOfMonth element (-dd)
067            we,  // weekyear element (xxxx)
068            wwe, // weekOfWeekyear element (-ww)
069            dwe, // dayOfWeek element (-ee)
070            dye, // dayOfYear element (-DDD)
071            hde, // hourOfDay element (HH)
072            mhe, // minuteOfHour element (:mm)
073            sme, // secondOfMinute element (:ss)
074            fse, // fractionOfSecond element (.SSSSSSSSS)
075            ze,  // zone offset element
076            lte, // literal 'T' element
077            
078            //y,   // year (same as year element)
079            ym,  // year month
080            ymd, // year month day
081    
082            //w,   // weekyear (same as weekyear element)
083            ww,  // weekyear week
084            wwd, // weekyear week day
085    
086            //h,    // hour (same as hour element)
087            hm,   // hour minute
088            hms,  // hour minute second
089            hmsl, // hour minute second millis
090            hmsf, // hour minute second fraction
091    
092            dh,    // date hour
093            dhm,   // date hour minute
094            dhms,  // date hour minute second
095            dhmsl, // date hour minute second millis
096            dhmsf, // date hour minute second fraction
097    
098            //d,  // date (same as ymd)
099            t,  // time
100            tx,  // time no millis
101            tt,  // Ttime
102            ttx,  // Ttime no millis
103            dt, // date time
104            dtx, // date time no millis
105    
106            //wd,  // week date (same as wwd)
107            wdt, // week date time
108            wdtx, // week date time no millis
109    
110            od,  // ordinal date (same as yd)
111            odt, // ordinal date time
112            odtx, // ordinal date time no millis
113    
114            bd,  // basic date
115            bt,  // basic time
116            btx,  // basic time no millis
117            btt, // basic Ttime
118            bttx, // basic Ttime no millis
119            bdt, // basic date time
120            bdtx, // basic date time no millis
121    
122            bod,  // basic ordinal date
123            bodt, // basic ordinal date time
124            bodtx, // basic ordinal date time no millis
125    
126            bwd,  // basic week date
127            bwdt, // basic week date time
128            bwdtx, // basic week date time no millis
129    
130            dpe, // date parser element
131            tpe, // time parser element
132            dp,  // date parser
133            ldp, // local date parser
134            tp,  // time parser
135            ltp, // local time parser
136            dtp, // date time parser
137            dotp, // date optional time parser
138            ldotp; // local date optional time parser
139    
140        /**
141         * Constructor.
142         *
143         * @since 1.1 (previously private)
144         */
145        protected ISODateTimeFormat() {
146            super();
147        }
148    
149        //-----------------------------------------------------------------------
150        /**
151         * Returns a formatter that outputs only those fields specified.
152         * <p>
153         * This method examines the fields provided and returns an ISO-style
154         * formatter that best fits. This can be useful for outputting
155         * less-common ISO styles, such as YearMonth (YYYY-MM) or MonthDay (--MM-DD).
156         * <p>
157         * The list provided may have overlapping fields, such as dayOfWeek and
158         * dayOfMonth. In this case, the style is chosen based on the following
159         * list, thus in the example, the calendar style is chosen as dayOfMonth
160         * is higher in priority than dayOfWeek:
161         * <ul>
162         * <li>monthOfYear - calendar date style
163         * <li>dayOfYear - ordinal date style
164         * <li>weekOfWeekYear - week date style
165         * <li>dayOfMonth - calendar date style
166         * <li>dayOfWeek - week date style
167         * <li>year
168         * <li>weekyear
169         * </ul>
170         * The supported formats are:
171         * <pre>
172         * Extended      Basic       Fields
173         * 2005-03-25    20050325    year/monthOfYear/dayOfMonth
174         * 2005-03       2005-03     year/monthOfYear
175         * 2005--25      2005--25    year/dayOfMonth *
176         * 2005          2005        year
177         * --03-25       --0325      monthOfYear/dayOfMonth
178         * --03          --03        monthOfYear
179         * ---03         ---03       dayOfMonth
180         * 2005-084      2005084     year/dayOfYear
181         * -084          -084        dayOfYear
182         * 2005-W12-5    2005W125    weekyear/weekOfWeekyear/dayOfWeek
183         * 2005-W-5      2005W-5     weekyear/dayOfWeek *
184         * 2005-W12      2005W12     weekyear/weekOfWeekyear
185         * -W12-5        -W125       weekOfWeekyear/dayOfWeek
186         * -W12          -W12        weekOfWeekyear
187         * -W-5          -W-5        dayOfWeek
188         * 10:20:30.040  102030.040  hour/minute/second/milli
189         * 10:20:30      102030      hour/minute/second
190         * 10:20         1020        hour/minute
191         * 10            10          hour
192         * -20:30.040    -2030.040   minute/second/milli
193         * -20:30        -2030       minute/second
194         * -20           -20         minute
195         * --30.040      --30.040    second/milli
196         * --30          --30        second
197         * ---.040       ---.040     milli *
198         * 10-30.040     10-30.040   hour/second/milli *
199         * 10:20-.040    1020-.040   hour/minute/milli *
200         * 10-30         10-30       hour/second *
201         * 10--.040      10--.040    hour/milli *
202         * -20-.040      -20-.040    minute/milli *
203         *   plus datetime formats like {date}T{time}
204         * </pre>
205         * * indiates that this is not an official ISO format and can be excluded
206         * by passing in <code>strictISO</code> as <code>true</code>.
207         * <p>
208         * This method can side effect the input collection of fields.
209         * If the input collection is modifiable, then each field that was added to
210         * the formatter will be removed from the collection, including any duplicates.
211         * If the input collection is unmodifiable then no side effect occurs.
212         * <p>
213         * This side effect processing is useful if you need to know whether all
214         * the fields were converted into the formatter or not. To achieve this,
215         * pass in a modifiable list, and check that it is empty on exit.
216         *
217         * @param fields  the fields to get a formatter for, not null,
218         *  updated by the method call unless unmodifiable,
219         *  removing those fields built in the formatter
220         * @param extended  true to use the extended format (with separators)
221         * @param strictISO  true to stick exactly to ISO8601, false to include additional formats
222         * @return a suitable formatter
223         * @throws IllegalArgumentException if there is no format for the fields
224         * @since 1.1
225         */
226        public static DateTimeFormatter forFields(
227            Collection fields,
228            boolean extended,
229            boolean strictISO) {
230            
231            if (fields == null || fields.size() == 0) {
232                throw new IllegalArgumentException("The fields must not be null or empty");
233            }
234            Set workingFields = new HashSet(fields);
235            int inputSize = workingFields.size();
236            boolean reducedPrec = false;
237            DateTimeFormatterBuilder bld = new DateTimeFormatterBuilder();
238            // date
239            if (workingFields.contains(DateTimeFieldType.monthOfYear())) {
240                reducedPrec = dateByMonth(bld, workingFields, extended, strictISO);
241            } else if (workingFields.contains(DateTimeFieldType.dayOfYear())) {
242                reducedPrec = dateByOrdinal(bld, workingFields, extended, strictISO);
243            } else if (workingFields.contains(DateTimeFieldType.weekOfWeekyear())) {
244                reducedPrec = dateByWeek(bld, workingFields, extended, strictISO);
245            } else if (workingFields.contains(DateTimeFieldType.dayOfMonth())) {
246                reducedPrec = dateByMonth(bld, workingFields, extended, strictISO);
247            } else if (workingFields.contains(DateTimeFieldType.dayOfWeek())) {
248                reducedPrec = dateByWeek(bld, workingFields, extended, strictISO);
249            } else if (workingFields.remove(DateTimeFieldType.year())) {
250                bld.append(yearElement());
251                reducedPrec = true;
252            } else if (workingFields.remove(DateTimeFieldType.weekyear())) {
253                bld.append(weekyearElement());
254                reducedPrec = true;
255            }
256            boolean datePresent = (workingFields.size() < inputSize);
257            
258            // time
259            time(bld, workingFields, extended, strictISO, reducedPrec, datePresent);
260            
261            // result
262            if (bld.canBuildFormatter() == false) {
263                throw new IllegalArgumentException("No valid format for fields: " + fields);
264            }
265            
266            // side effect the input collection to indicate the processed fields
267            // handling unmodifiable collections with no side effect
268            try {
269                fields.retainAll(workingFields);
270            } catch (UnsupportedOperationException ex) {
271                // ignore, so we can handle unmodifiable collections
272            }
273            return bld.toFormatter();
274        }
275    
276        //-----------------------------------------------------------------------
277        /**
278         * Creates a date using the calendar date format.
279         * Specification reference: 5.2.1.
280         *
281         * @param bld  the builder
282         * @param fields  the fields
283         * @param extended  true to use extended format
284         * @param strictISO  true to only allow ISO formats
285         * @return true if reduced precision
286         * @since 1.1
287         */
288        private static boolean dateByMonth(
289            DateTimeFormatterBuilder bld,
290            Collection fields,
291            boolean extended,
292            boolean strictISO) {
293            
294            boolean reducedPrec = false;
295            if (fields.remove(DateTimeFieldType.year())) {
296                bld.append(yearElement());
297                if (fields.remove(DateTimeFieldType.monthOfYear())) {
298                    if (fields.remove(DateTimeFieldType.dayOfMonth())) {
299                        // YYYY-MM-DD/YYYYMMDD
300                        appendSeparator(bld, extended);
301                        bld.appendMonthOfYear(2);
302                        appendSeparator(bld, extended);
303                        bld.appendDayOfMonth(2);
304                    } else {
305                        // YYYY-MM/YYYY-MM
306                        bld.appendLiteral('-');
307                        bld.appendMonthOfYear(2);
308                        reducedPrec = true;
309                    }
310                } else {
311                    if (fields.remove(DateTimeFieldType.dayOfMonth())) {
312                        // YYYY--DD/YYYY--DD (non-iso)
313                        checkNotStrictISO(fields, strictISO);
314                        bld.appendLiteral('-');
315                        bld.appendLiteral('-');
316                        bld.appendDayOfMonth(2);
317                    } else {
318                        // YYYY/YYYY
319                        reducedPrec = true;
320                    }
321                }
322                
323            } else if (fields.remove(DateTimeFieldType.monthOfYear())) {
324                bld.appendLiteral('-');
325                bld.appendLiteral('-');
326                bld.appendMonthOfYear(2);
327                if (fields.remove(DateTimeFieldType.dayOfMonth())) {
328                    // --MM-DD/--MMDD
329                    appendSeparator(bld, extended);
330                    bld.appendDayOfMonth(2);
331                } else {
332                    // --MM/--MM
333                    reducedPrec = true;
334                }
335            } else if (fields.remove(DateTimeFieldType.dayOfMonth())) {
336                // ---DD/---DD
337                bld.appendLiteral('-');
338                bld.appendLiteral('-');
339                bld.appendLiteral('-');
340                bld.appendDayOfMonth(2);
341            }
342            return reducedPrec;
343        }
344    
345        //-----------------------------------------------------------------------
346        /**
347         * Creates a date using the ordinal date format.
348         * Specification reference: 5.2.2.
349         *
350         * @param bld  the builder
351         * @param fields  the fields
352         * @param extended  true to use extended format
353         * @param strictISO  true to only allow ISO formats
354         * @since 1.1
355         */
356        private static boolean dateByOrdinal(
357            DateTimeFormatterBuilder bld,
358            Collection fields,
359            boolean extended,
360            boolean strictISO) {
361            
362            boolean reducedPrec = false;
363            if (fields.remove(DateTimeFieldType.year())) {
364                bld.append(yearElement());
365                if (fields.remove(DateTimeFieldType.dayOfYear())) {
366                    // YYYY-DDD/YYYYDDD
367                    appendSeparator(bld, extended);
368                    bld.appendDayOfYear(3);
369                } else {
370                    // YYYY/YYYY
371                    reducedPrec = true;
372                }
373                
374            } else if (fields.remove(DateTimeFieldType.dayOfYear())) {
375                // -DDD/-DDD
376                bld.appendLiteral('-');
377                bld.appendDayOfYear(3);
378            }
379            return reducedPrec;
380        }
381    
382        //-----------------------------------------------------------------------
383        /**
384         * Creates a date using the calendar date format.
385         * Specification reference: 5.2.3.
386         *
387         * @param bld  the builder
388         * @param fields  the fields
389         * @param extended  true to use extended format
390         * @param strictISO  true to only allow ISO formats
391         * @since 1.1
392         */
393        private static boolean dateByWeek(
394            DateTimeFormatterBuilder bld,
395            Collection fields,
396            boolean extended,
397            boolean strictISO) {
398            
399            boolean reducedPrec = false;
400            if (fields.remove(DateTimeFieldType.weekyear())) {
401                bld.append(weekyearElement());
402                if (fields.remove(DateTimeFieldType.weekOfWeekyear())) {
403                    appendSeparator(bld, extended);
404                    bld.appendLiteral('W');
405                    bld.appendWeekOfWeekyear(2);
406                    if (fields.remove(DateTimeFieldType.dayOfWeek())) {
407                        // YYYY-WWW-D/YYYYWWWD
408                        appendSeparator(bld, extended);
409                        bld.appendDayOfWeek(1);
410                    } else {
411                        // YYYY-WWW/YYYY-WWW
412                        reducedPrec = true;
413                    }
414                } else {
415                    if (fields.remove(DateTimeFieldType.dayOfWeek())) {
416                        // YYYY-W-D/YYYYW-D (non-iso)
417                        checkNotStrictISO(fields, strictISO);
418                        appendSeparator(bld, extended);
419                        bld.appendLiteral('W');
420                        bld.appendLiteral('-');
421                        bld.appendDayOfWeek(1);
422                    } else {
423                        // YYYY/YYYY
424                        reducedPrec = true;
425                    }
426                }
427                
428            } else if (fields.remove(DateTimeFieldType.weekOfWeekyear())) {
429                bld.appendLiteral('-');
430                bld.appendLiteral('W');
431                bld.appendWeekOfWeekyear(2);
432                if (fields.remove(DateTimeFieldType.dayOfWeek())) {
433                    // -WWW-D/-WWWD
434                    appendSeparator(bld, extended);
435                    bld.appendDayOfWeek(1);
436                } else {
437                    // -WWW/-WWW
438                    reducedPrec = true;
439                }
440            } else if (fields.remove(DateTimeFieldType.dayOfWeek())) {
441                // -W-D/-W-D
442                bld.appendLiteral('-');
443                bld.appendLiteral('W');
444                bld.appendLiteral('-');
445                bld.appendDayOfWeek(1);
446            }
447            return reducedPrec;
448        }
449    
450        //-----------------------------------------------------------------------
451        /**
452         * Adds the time fields to the builder.
453         * Specification reference: 5.3.1.
454         * 
455         * @param bld  the builder
456         * @param fields  the fields
457         * @param extended  whether to use the extended format
458         * @param strictISO  whether to be strict
459         * @param reducedPrec  whether the date was reduced precision
460         * @param datePresent  whether there was a date
461         * @since 1.1
462         */
463        private static void time(
464            DateTimeFormatterBuilder bld,
465            Collection fields,
466            boolean extended,
467            boolean strictISO,
468            boolean reducedPrec,
469            boolean datePresent) {
470            
471            boolean hour = fields.remove(DateTimeFieldType.hourOfDay());
472            boolean minute = fields.remove(DateTimeFieldType.minuteOfHour());
473            boolean second = fields.remove(DateTimeFieldType.secondOfMinute());
474            boolean milli = fields.remove(DateTimeFieldType.millisOfSecond());
475            if (!hour && !minute && !second && !milli) {
476                return;
477            }
478            if (hour || minute || second || milli) {
479                if (strictISO && reducedPrec) {
480                    throw new IllegalArgumentException("No valid ISO8601 format for fields because Date was reduced precision: " + fields);
481                }
482                if (datePresent) {
483                    bld.appendLiteral('T');
484                }
485            }
486            if (hour && minute && second || (hour && !second && !milli)) {
487                // OK - HMSm/HMS/HM/H - valid in combination with date
488            } else {
489                if (strictISO && datePresent) {
490                    throw new IllegalArgumentException("No valid ISO8601 format for fields because Time was truncated: " + fields);
491                }
492                if (!hour && (minute && second || (minute && !milli) || second)) {
493                    // OK - MSm/MS/M/Sm/S - valid ISO formats
494                } else {
495                    if (strictISO) {
496                        throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields);
497                    }
498                }
499            }
500            if (hour) {
501                bld.appendHourOfDay(2);
502            } else if (minute || second || milli) {
503                bld.appendLiteral('-');
504            }
505            if (extended && hour && minute) {
506                bld.appendLiteral(':');
507            }
508            if (minute) {
509                bld.appendMinuteOfHour(2);
510            } else if (second || milli) {
511                bld.appendLiteral('-');
512            }
513            if (extended && minute && second) {
514                bld.appendLiteral(':');
515            }
516            if (second) {
517                bld.appendSecondOfMinute(2);
518            } else if (milli) {
519                bld.appendLiteral('-');
520            }
521            if (milli) {
522                bld.appendLiteral('.');
523                bld.appendMillisOfSecond(3);
524            }
525        }
526    
527        //-----------------------------------------------------------------------
528        /**
529         * Checks that the iso only flag is not set, throwing an exception if it is.
530         * 
531         * @param fields  the fields
532         * @param strictISO  true if only ISO formats allowed
533         * @since 1.1
534         */
535        private static void checkNotStrictISO(Collection fields, boolean strictISO) {
536            if (strictISO) {
537                throw new IllegalArgumentException("No valid ISO8601 format for fields: " + fields);
538            }
539        }
540    
541        /**
542         * Appends the separator if necessary.
543         *
544         * @param bld  the builder
545         * @param extended  whether to append the separator
546         * @param sep  the separator
547         * @since 1.1
548         */
549        private static void appendSeparator(DateTimeFormatterBuilder bld, boolean extended) {
550            if (extended) {
551                bld.appendLiteral('-');
552            }
553        }
554    
555        //-----------------------------------------------------------------------
556        /**
557         * Returns a generic ISO date parser for parsing dates with a possible zone.
558         * It accepts formats described by the following syntax:
559         * <pre>
560         * date              = date-element ['T' offset]
561         * date-element      = std-date-element | ord-date-element | week-date-element
562         * std-date-element  = yyyy ['-' MM ['-' dd]]
563         * ord-date-element  = yyyy ['-' DDD]
564         * week-date-element = xxxx '-W' ww ['-' e]
565         * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
566         * </pre>
567         */
568        public static DateTimeFormatter dateParser() {
569            if (dp == null) {
570                DateTimeParser tOffset = new DateTimeFormatterBuilder()
571                    .appendLiteral('T')
572                    .append(offsetElement()).toParser();
573                dp = new DateTimeFormatterBuilder()
574                    .append(dateElementParser())
575                    .appendOptional(tOffset)
576                    .toFormatter();
577            }
578            return dp;
579        }
580    
581        /**
582         * Returns a generic ISO date parser for parsing local dates.
583         * This parser is initialised with the local (UTC) time zone.
584         * <p>
585         * It accepts formats described by the following syntax:
586         * <pre>
587         * date-element      = std-date-element | ord-date-element | week-date-element
588         * std-date-element  = yyyy ['-' MM ['-' dd]]
589         * ord-date-element  = yyyy ['-' DDD]
590         * week-date-element = xxxx '-W' ww ['-' e]
591         * </pre>
592         * @since 1.3
593         */
594        public static DateTimeFormatter localDateParser() {
595            if (ldp == null) {
596                ldp = dateElementParser().withZone(DateTimeZone.UTC);
597            }
598            return ldp;
599        }
600    
601        /**
602         * Returns a generic ISO date parser for parsing dates.
603         * It accepts formats described by the following syntax:
604         * <pre>
605         * date-element      = std-date-element | ord-date-element | week-date-element
606         * std-date-element  = yyyy ['-' MM ['-' dd]]
607         * ord-date-element  = yyyy ['-' DDD]
608         * week-date-element = xxxx '-W' ww ['-' e]
609         * </pre>
610         */
611        public static DateTimeFormatter dateElementParser() {
612            if (dpe == null) {
613                dpe = new DateTimeFormatterBuilder()
614                    .append(null, new DateTimeParser[] {
615                        new DateTimeFormatterBuilder()
616                        .append(yearElement())
617                        .appendOptional
618                        (new DateTimeFormatterBuilder()
619                         .append(monthElement())
620                         .appendOptional(dayOfMonthElement().getParser())
621                         .toParser())
622                        .toParser(),
623                        new DateTimeFormatterBuilder()
624                        .append(weekyearElement())
625                        .append(weekElement())
626                        .appendOptional(dayOfWeekElement().getParser())
627                        .toParser(),
628                        new DateTimeFormatterBuilder()
629                        .append(yearElement())
630                        .append(dayOfYearElement())
631                        .toParser()
632                    })
633                    .toFormatter();
634            }
635            return dpe;
636        }
637    
638        /**
639         * Returns a generic ISO time parser for parsing times with a possible zone.
640         * It accepts formats described by the following syntax:
641         * <pre>
642         * time           = ['T'] time-element [offset]
643         * time-element   = HH [minute-element] | [fraction]
644         * minute-element = ':' mm [second-element] | [fraction]
645         * second-element = ':' ss [fraction]
646         * fraction       = ('.' | ',') digit+
647         * offset         = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
648         * </pre>
649         */
650        public static DateTimeFormatter timeParser() {
651            if (tp == null) {
652                tp = new DateTimeFormatterBuilder()
653                    .appendOptional(literalTElement().getParser())
654                    .append(timeElementParser())
655                    .appendOptional(offsetElement().getParser())
656                    .toFormatter();
657            }
658            return tp;
659        }
660    
661        /**
662         * Returns a generic ISO time parser for parsing local times.
663         * This parser is initialised with the local (UTC) time zone.
664         * <p>
665         * It accepts formats described by the following syntax:
666         * <pre>
667         * time           = ['T'] time-element
668         * time-element   = HH [minute-element] | [fraction]
669         * minute-element = ':' mm [second-element] | [fraction]
670         * second-element = ':' ss [fraction]
671         * fraction       = ('.' | ',') digit+
672         * </pre>
673         * @since 1.3
674         */
675        public static DateTimeFormatter localTimeParser() {
676            if (ltp == null) {
677                ltp = new DateTimeFormatterBuilder()
678                    .appendOptional(literalTElement().getParser())
679                    .append(timeElementParser())
680                    .toFormatter().withZone(DateTimeZone.UTC);
681            }
682            return ltp;
683        }
684    
685        /**
686         * Returns a generic ISO time parser. It accepts formats described by
687         * the following syntax:
688         * <pre>
689         * time-element   = HH [minute-element] | [fraction]
690         * minute-element = ':' mm [second-element] | [fraction]
691         * second-element = ':' ss [fraction]
692         * fraction       = ('.' | ',') digit+
693         * </pre>
694         */
695        public static DateTimeFormatter timeElementParser() {
696            if (tpe == null) {
697                // Decimal point can be either '.' or ','
698                DateTimeParser decimalPoint = new DateTimeFormatterBuilder()
699                    .append(null, new DateTimeParser[] {
700                        new DateTimeFormatterBuilder()
701                        .appendLiteral('.')
702                        .toParser(),
703                        new DateTimeFormatterBuilder()
704                        .appendLiteral(',')
705                        .toParser()
706                    })
707                    .toParser();
708    
709                tpe = new DateTimeFormatterBuilder()
710                    // time-element
711                    .append(hourElement())
712                    .append
713                    (null, new DateTimeParser[] {
714                        new DateTimeFormatterBuilder()
715                        // minute-element
716                        .append(minuteElement())
717                        .append
718                        (null, new DateTimeParser[] {
719                            new DateTimeFormatterBuilder()
720                            // second-element
721                            .append(secondElement())
722                            // second fraction
723                            .appendOptional(new DateTimeFormatterBuilder()
724                                            .append(decimalPoint)
725                                            .appendFractionOfSecond(1, 9)
726                                            .toParser())
727                            .toParser(),
728                            // minute fraction
729                            new DateTimeFormatterBuilder()
730                            .append(decimalPoint)
731                            .appendFractionOfMinute(1, 9)
732                            .toParser(),
733                            null
734                        })
735                        .toParser(),
736                        // hour fraction
737                        new DateTimeFormatterBuilder()
738                        .append(decimalPoint)
739                        .appendFractionOfHour(1, 9)
740                        .toParser(),
741                        null
742                    })
743                    .toFormatter();
744            }
745            return tpe;
746        }
747    
748        /**
749         * Returns a generic ISO datetime parser which parses either a date or
750         * a time or both. It accepts formats described by the following syntax:
751         * <pre>
752         * datetime          = time | date-opt-time
753         * time              = 'T' time-element [offset]
754         * date-opt-time     = date-element ['T' [time-element] [offset]]
755         * date-element      = std-date-element | ord-date-element | week-date-element
756         * std-date-element  = yyyy ['-' MM ['-' dd]]
757         * ord-date-element  = yyyy ['-' DDD]
758         * week-date-element = xxxx '-W' ww ['-' e]
759         * time-element      = HH [minute-element] | [fraction]
760         * minute-element    = ':' mm [second-element] | [fraction]
761         * second-element    = ':' ss [fraction]
762         * fraction          = ('.' | ',') digit+
763         * offset            = 'Z' | (('+' | '-') HH [':' mm [':' ss [('.' | ',') SSS]]])
764         * </pre>
765         */
766        public static DateTimeFormatter dateTimeParser() {
767            if (dtp == null) {
768                // This is different from the general time parser in that the 'T'
769                // is required.
770                DateTimeParser time = new DateTimeFormatterBuilder()
771                    .appendLiteral('T')
772                    .append(timeElementParser())
773                    .appendOptional(offsetElement().getParser())
774                    .toParser();
775                dtp = new DateTimeFormatterBuilder()
776                    .append(null, new DateTimeParser[] {time, dateOptionalTimeParser().getParser()})
777                    .toFormatter();
778            }
779            return dtp;
780        }
781    
782        /**
783         * Returns a generic ISO datetime parser where the date is mandatory and
784         * the time is optional. This parser can parse zoned datetimes.
785         * It accepts formats described by the following syntax:
786         * <pre>
787         * date-opt-time     = date-element ['T' [time-element] [offset]]
788         * date-element      = std-date-element | ord-date-element | week-date-element
789         * std-date-element  = yyyy ['-' MM ['-' dd]]
790         * ord-date-element  = yyyy ['-' DDD]
791         * week-date-element = xxxx '-W' ww ['-' e]
792         * time-element      = HH [minute-element] | [fraction]
793         * minute-element    = ':' mm [second-element] | [fraction]
794         * second-element    = ':' ss [fraction]
795         * fraction          = ('.' | ',') digit+
796         * </pre>
797         * @since 1.3
798         */
799        public static DateTimeFormatter dateOptionalTimeParser() {
800            if (dotp == null) {
801                DateTimeParser timeOrOffset = new DateTimeFormatterBuilder()
802                    .appendLiteral('T')
803                    .appendOptional(timeElementParser().getParser())
804                    .appendOptional(offsetElement().getParser())
805                    .toParser();
806                dotp = new DateTimeFormatterBuilder()
807                    .append(dateElementParser())
808                    .appendOptional(timeOrOffset)
809                    .toFormatter();
810            }
811            return dotp;
812        }
813    
814        /**
815         * Returns a generic ISO datetime parser where the date is mandatory and
816         * the time is optional. This parser only parses local datetimes.
817         * This parser is initialised with the local (UTC) time zone.
818         * <p>
819         * It accepts formats described by the following syntax:
820         * <pre>
821         * datetime          = date-element ['T' time-element]
822         * date-element      = std-date-element | ord-date-element | week-date-element
823         * std-date-element  = yyyy ['-' MM ['-' dd]]
824         * ord-date-element  = yyyy ['-' DDD]
825         * week-date-element = xxxx '-W' ww ['-' e]
826         * time-element      = HH [minute-element] | [fraction]
827         * minute-element    = ':' mm [second-element] | [fraction]
828         * second-element    = ':' ss [fraction]
829         * fraction          = ('.' | ',') digit+
830         * </pre>
831         * @since 1.3
832         */
833        public static DateTimeFormatter localDateOptionalTimeParser() {
834            if (ldotp == null) {
835                DateTimeParser time = new DateTimeFormatterBuilder()
836                    .appendLiteral('T')
837                    .append(timeElementParser())
838                    .toParser();
839                ldotp = new DateTimeFormatterBuilder()
840                    .append(dateElementParser())
841                    .appendOptional(time)
842                    .toFormatter().withZone(DateTimeZone.UTC);
843            }
844            return ldotp;
845        }
846    
847        //-----------------------------------------------------------------------
848        /**
849         * Returns a formatter for a full date as four digit year, two digit month
850         * of year, and two digit day of month (yyyy-MM-dd).
851         * 
852         * @return a formatter for yyyy-MM-dd
853         */
854        public static DateTimeFormatter date() {
855            return yearMonthDay();
856        }
857    
858        /**
859         * Returns a formatter for a two digit hour of day, two digit minute of
860         * hour, two digit second of minute, three digit fraction of second, and
861         * time zone offset (HH:mm:ss.SSSZZ).
862         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
863         * 
864         * @return a formatter for HH:mm:ss.SSSZZ
865         */
866        public static DateTimeFormatter time() {
867            if (t == null) {
868                t = new DateTimeFormatterBuilder()
869                    .append(hourMinuteSecondFraction())
870                    .append(offsetElement())
871                    .toFormatter();
872            }
873            return t;
874        }
875    
876        /**
877         * Returns a formatter for a two digit hour of day, two digit minute of
878         * hour, two digit second of minute, and time zone offset (HH:mm:ssZZ).
879         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
880         * 
881         * @return a formatter for HH:mm:ssZZ
882         */
883        public static DateTimeFormatter timeNoMillis() {
884            if (tx == null) {
885                tx = new DateTimeFormatterBuilder()
886                    .append(hourMinuteSecond())
887                    .append(offsetElement())
888                    .toFormatter();
889            }
890            return tx;
891        }
892    
893        /**
894         * Returns a formatter for a two digit hour of day, two digit minute of
895         * hour, two digit second of minute, three digit fraction of second, and
896         * time zone offset prefixed by 'T' ('T'HH:mm:ss.SSSZZ).
897         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
898         * 
899         * @return a formatter for 'T'HH:mm:ss.SSSZZ
900         */
901        public static DateTimeFormatter tTime() {
902            if (tt == null) {
903                tt = new DateTimeFormatterBuilder()
904                    .append(literalTElement())
905                    .append(time())
906                    .toFormatter();
907            }
908            return tt;
909        }
910    
911        /**
912         * Returns a formatter for a two digit hour of day, two digit minute of
913         * hour, two digit second of minute, and time zone offset prefixed
914         * by 'T' ('T'HH:mm:ssZZ).
915         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
916         * 
917         * @return a formatter for 'T'HH:mm:ssZZ
918         */
919        public static DateTimeFormatter tTimeNoMillis() {
920            if (ttx == null) {
921                ttx = new DateTimeFormatterBuilder()
922                    .append(literalTElement())
923                    .append(timeNoMillis())
924                    .toFormatter();
925            }
926            return ttx;
927        }
928    
929        /**
930         * Returns a formatter that combines a full date and time, separated by a 'T'
931         * (yyyy-MM-dd'T'HH:mm:ss.SSSZZ).
932         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
933         * 
934         * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSSZZ
935         */
936        public static DateTimeFormatter dateTime() {
937            if (dt == null) {
938                dt = new DateTimeFormatterBuilder()
939                    .append(date())
940                    .append(tTime())
941                    .toFormatter();
942            }
943            return dt;
944        }
945    
946        /**
947         * Returns a formatter that combines a full date and time without millis,
948         * separated by a 'T' (yyyy-MM-dd'T'HH:mm:ssZZ).
949         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
950         * 
951         * @return a formatter for yyyy-MM-dd'T'HH:mm:ssZZ
952         */
953        public static DateTimeFormatter dateTimeNoMillis() {
954            if (dtx == null) {
955                dtx = new DateTimeFormatterBuilder()
956                    .append(date())
957                    .append(tTimeNoMillis())
958                    .toFormatter();
959            }
960            return dtx;
961        }
962    
963        /**
964         * Returns a formatter for a full ordinal date, using a four
965         * digit year and three digit dayOfYear (yyyy-DDD).
966         * 
967         * @return a formatter for yyyy-DDD
968         * @since 1.1
969         */
970        public static DateTimeFormatter ordinalDate() {
971            if (od == null) {
972                od = new DateTimeFormatterBuilder()
973                    .append(yearElement())
974                    .append(dayOfYearElement())
975                    .toFormatter();
976            }
977            return od;
978        }
979    
980        /**
981         * Returns a formatter for a full ordinal date and time, using a four
982         * digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ss.SSSZZ).
983         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
984         * 
985         * @return a formatter for yyyy-DDD'T'HH:mm:ss.SSSZZ
986         * @since 1.1
987         */
988        public static DateTimeFormatter ordinalDateTime() {
989            if (odt == null) {
990                odt = new DateTimeFormatterBuilder()
991                    .append(ordinalDate())
992                    .append(tTime())
993                    .toFormatter();
994            }
995            return odt;
996        }
997    
998        /**
999         * Returns a formatter for a full ordinal date and time without millis,
1000         * using a four digit year and three digit dayOfYear (yyyy-DDD'T'HH:mm:ssZZ).
1001         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
1002         * 
1003         * @return a formatter for yyyy-DDD'T'HH:mm:ssZZ
1004         * @since 1.1
1005         */
1006        public static DateTimeFormatter ordinalDateTimeNoMillis() {
1007            if (odtx == null) {
1008                odtx = new DateTimeFormatterBuilder()
1009                    .append(ordinalDate())
1010                    .append(tTimeNoMillis())
1011                    .toFormatter();
1012            }
1013            return odtx;
1014        }
1015    
1016        /**
1017         * Returns a formatter for a full date as four digit weekyear, two digit
1018         * week of weekyear, and one digit day of week (xxxx-'W'ww-e).
1019         * 
1020         * @return a formatter for xxxx-'W'ww-e
1021         */
1022        public static DateTimeFormatter weekDate() {
1023            return weekyearWeekDay();
1024        }
1025    
1026        /**
1027         * Returns a formatter that combines a full weekyear date and time,
1028         * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ).
1029         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
1030         * 
1031         * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ
1032         */
1033        public static DateTimeFormatter weekDateTime() {
1034            if (wdt == null) {
1035                wdt = new DateTimeFormatterBuilder()
1036                    .append(weekDate())
1037                    .append(tTime())
1038                    .toFormatter();
1039            }
1040            return wdt;
1041        }
1042    
1043        /**
1044         * Returns a formatter that combines a full weekyear date and time without millis,
1045         * separated by a 'T' (xxxx-'W'ww-e'T'HH:mm:ssZZ).
1046         * The time zone offset is 'Z' for zero, and of the form '\u00b1HH:mm' for non-zero.
1047         * 
1048         * @return a formatter for xxxx-'W'ww-e'T'HH:mm:ssZZ
1049         */
1050        public static DateTimeFormatter weekDateTimeNoMillis() {
1051            if (wdtx == null) {
1052                wdtx = new DateTimeFormatterBuilder()
1053                    .append(weekDate())
1054                    .append(tTimeNoMillis())
1055                    .toFormatter();
1056            }
1057            return wdtx;
1058        }
1059    
1060        //-----------------------------------------------------------------------
1061        /**
1062         * Returns a basic formatter for a full date as four digit year, two digit
1063         * month of year, and two digit day of month (yyyyMMdd).
1064         * 
1065         * @return a formatter for yyyyMMdd
1066         */
1067        public static DateTimeFormatter basicDate() {
1068            if (bd == null) {
1069                bd = new DateTimeFormatterBuilder()
1070                    .appendYear(4, 4)
1071                    .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2)
1072                    .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2)
1073                    .toFormatter();
1074            }
1075            return bd;
1076        }
1077    
1078        /**
1079         * Returns a basic formatter for a two digit hour of day, two digit minute
1080         * of hour, two digit second of minute, three digit millis, and time zone
1081         * offset (HHmmss.SSSZ).
1082         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1083         * 
1084         * @return a formatter for HHmmss.SSSZ
1085         */
1086        public static DateTimeFormatter basicTime() {
1087            if (bt == null) {
1088                bt = new DateTimeFormatterBuilder()
1089                    .appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2)
1090                    .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2)
1091                    .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2)
1092                    .appendLiteral('.')
1093                    .appendFractionOfSecond(3, 9)
1094                    .appendTimeZoneOffset("Z", false, 2, 2)
1095                    .toFormatter();
1096            }
1097            return bt;
1098        }
1099    
1100        /**
1101         * Returns a basic formatter for a two digit hour of day, two digit minute
1102         * of hour, two digit second of minute, and time zone offset (HHmmssZ).
1103         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1104         * 
1105         * @return a formatter for HHmmssZ
1106         */
1107        public static DateTimeFormatter basicTimeNoMillis() {
1108            if (btx == null) {
1109                btx = new DateTimeFormatterBuilder()
1110                    .appendFixedDecimal(DateTimeFieldType.hourOfDay(), 2)
1111                    .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2)
1112                    .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2)
1113                    .appendTimeZoneOffset("Z", false, 2, 2)
1114                    .toFormatter();
1115            }
1116            return btx;
1117        }
1118    
1119        /**
1120         * Returns a basic formatter for a two digit hour of day, two digit minute
1121         * of hour, two digit second of minute, three digit millis, and time zone
1122         * offset prefixed by 'T' ('T'HHmmss.SSSZ).
1123         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1124         * 
1125         * @return a formatter for 'T'HHmmss.SSSZ
1126         */
1127        public static DateTimeFormatter basicTTime() {
1128            if (btt == null) {
1129                btt = new DateTimeFormatterBuilder()
1130                    .append(literalTElement())
1131                    .append(basicTime())
1132                    .toFormatter();
1133            }
1134            return btt;
1135        }
1136    
1137        /**
1138         * Returns a basic formatter for a two digit hour of day, two digit minute
1139         * of hour, two digit second of minute, and time zone offset prefixed by 'T'
1140         * ('T'HHmmssZ).
1141         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1142         * 
1143         * @return a formatter for 'T'HHmmssZ
1144         */
1145        public static DateTimeFormatter basicTTimeNoMillis() {
1146            if (bttx == null) {
1147                bttx = new DateTimeFormatterBuilder()
1148                    .append(literalTElement())
1149                    .append(basicTimeNoMillis())
1150                    .toFormatter();
1151            }
1152            return bttx;
1153        }
1154    
1155        /**
1156         * Returns a basic formatter that combines a basic date and time, separated
1157         * by a 'T' (yyyyMMdd'T'HHmmss.SSSZ).
1158         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1159         * 
1160         * @return a formatter for yyyyMMdd'T'HHmmss.SSSZ
1161         */
1162        public static DateTimeFormatter basicDateTime() {
1163            if (bdt == null) {
1164                bdt = new DateTimeFormatterBuilder()
1165                    .append(basicDate())
1166                    .append(basicTTime())
1167                    .toFormatter();
1168            }
1169            return bdt;
1170        }
1171    
1172        /**
1173         * Returns a basic formatter that combines a basic date and time without millis,
1174         * separated by a 'T' (yyyyMMdd'T'HHmmssZ).
1175         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1176         * 
1177         * @return a formatter for yyyyMMdd'T'HHmmssZ
1178         */
1179        public static DateTimeFormatter basicDateTimeNoMillis() {
1180            if (bdtx == null) {
1181                bdtx = new DateTimeFormatterBuilder()
1182                    .append(basicDate())
1183                    .append(basicTTimeNoMillis())
1184                    .toFormatter();
1185            }
1186            return bdtx;
1187        }
1188    
1189        /**
1190         * Returns a formatter for a full ordinal date, using a four
1191         * digit year and three digit dayOfYear (yyyyDDD).
1192         * 
1193         * @return a formatter for yyyyDDD
1194         * @since 1.1
1195         */
1196        public static DateTimeFormatter basicOrdinalDate() {
1197            if (bod == null) {
1198                bod = new DateTimeFormatterBuilder()
1199                    .appendYear(4, 4)
1200                    .appendFixedDecimal(DateTimeFieldType.dayOfYear(), 3)
1201                    .toFormatter();
1202            }
1203            return bod;
1204        }
1205    
1206        /**
1207         * Returns a formatter for a full ordinal date and time, using a four
1208         * digit year and three digit dayOfYear (yyyyDDD'T'HHmmss.SSSZ).
1209         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1210         * 
1211         * @return a formatter for yyyyDDD'T'HHmmss.SSSZ
1212         * @since 1.1
1213         */
1214        public static DateTimeFormatter basicOrdinalDateTime() {
1215            if (bodt == null) {
1216                bodt = new DateTimeFormatterBuilder()
1217                    .append(basicOrdinalDate())
1218                    .append(basicTTime())
1219                    .toFormatter();
1220            }
1221            return bodt;
1222        }
1223    
1224        /**
1225         * Returns a formatter for a full ordinal date and time without millis,
1226         * using a four digit year and three digit dayOfYear (yyyyDDD'T'HHmmssZ).
1227         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1228         * 
1229         * @return a formatter for yyyyDDD'T'HHmmssZ
1230         * @since 1.1
1231         */
1232        public static DateTimeFormatter basicOrdinalDateTimeNoMillis() {
1233            if (bodtx == null) {
1234                bodtx = new DateTimeFormatterBuilder()
1235                    .append(basicOrdinalDate())
1236                    .append(basicTTimeNoMillis())
1237                    .toFormatter();
1238            }
1239            return bodtx;
1240        }
1241    
1242        /**
1243         * Returns a basic formatter for a full date as four digit weekyear, two
1244         * digit week of weekyear, and one digit day of week (xxxx'W'wwe).
1245         * 
1246         * @return a formatter for xxxx'W'wwe
1247         */
1248        public static DateTimeFormatter basicWeekDate() {
1249            if (bwd == null) {
1250                bwd = new DateTimeFormatterBuilder()
1251                    .appendWeekyear(4, 4)
1252                    .appendLiteral('W')
1253                    .appendFixedDecimal(DateTimeFieldType.weekOfWeekyear(), 2)
1254                    .appendFixedDecimal(DateTimeFieldType.dayOfWeek(), 1)
1255                    .toFormatter();
1256            }
1257            return bwd;
1258        }
1259    
1260        /**
1261         * Returns a basic formatter that combines a basic weekyear date and time,
1262         * separated by a 'T' (xxxx'W'wwe'T'HHmmss.SSSZ).
1263         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1264         * 
1265         * @return a formatter for xxxx'W'wwe'T'HHmmss.SSSZ
1266         */
1267        public static DateTimeFormatter basicWeekDateTime() {
1268            if (bwdt == null) {
1269                bwdt = new DateTimeFormatterBuilder()
1270                    .append(basicWeekDate())
1271                    .append(basicTTime())
1272                    .toFormatter();
1273            }
1274            return bwdt;
1275        }
1276    
1277        /**
1278         * Returns a basic formatter that combines a basic weekyear date and time
1279         * without millis, separated by a 'T' (xxxx'W'wwe'T'HHmmssZ).
1280         * The time zone offset is 'Z' for zero, and of the form '\u00b1HHmm' for non-zero.
1281         * 
1282         * @return a formatter for xxxx'W'wwe'T'HHmmssZ
1283         */
1284        public static DateTimeFormatter basicWeekDateTimeNoMillis() {
1285            if (bwdtx == null) {
1286                bwdtx = new DateTimeFormatterBuilder()
1287                    .append(basicWeekDate())
1288                    .append(basicTTimeNoMillis())
1289                    .toFormatter();
1290            }
1291            return bwdtx;
1292        }
1293    
1294        //-----------------------------------------------------------------------
1295        /**
1296         * Returns a formatter for a four digit year. (yyyy)
1297         * 
1298         * @return a formatter for yyyy
1299         */
1300        public static DateTimeFormatter year() {
1301            return yearElement();
1302        }
1303    
1304        /**
1305         * Returns a formatter for a four digit year and two digit month of
1306         * year. (yyyy-MM)
1307         * 
1308         * @return a formatter for yyyy-MM
1309         */
1310        public static DateTimeFormatter yearMonth() {
1311            if (ym == null) {
1312                ym = new DateTimeFormatterBuilder()
1313                    .append(yearElement())
1314                    .append(monthElement())
1315                    .toFormatter();
1316            }
1317            return ym;
1318        }
1319    
1320        /**
1321         * Returns a formatter for a four digit year, two digit month of year, and
1322         * two digit day of month. (yyyy-MM-dd)
1323         * 
1324         * @return a formatter for yyyy-MM-dd
1325         */
1326        public static DateTimeFormatter yearMonthDay() {
1327            if (ymd == null) {
1328                ymd = new DateTimeFormatterBuilder()
1329                    .append(yearElement())
1330                    .append(monthElement())
1331                    .append(dayOfMonthElement())
1332                    .toFormatter();
1333            }
1334            return ymd;
1335        }
1336    
1337        /**
1338         * Returns a formatter for a four digit weekyear. (xxxx)
1339         * 
1340         * @return a formatter for xxxx
1341         */
1342        public static DateTimeFormatter weekyear() {
1343            return weekyearElement();
1344        }
1345    
1346        /**
1347         * Returns a formatter for a four digit weekyear and two digit week of
1348         * weekyear. (xxxx-'W'ww)
1349         * 
1350         * @return a formatter for xxxx-'W'ww
1351         */
1352        public static DateTimeFormatter weekyearWeek() {
1353            if (ww == null) {
1354                ww = new DateTimeFormatterBuilder()
1355                    .append(weekyearElement())
1356                    .append(weekElement())
1357                    .toFormatter();
1358            }
1359            return ww;
1360        }
1361    
1362        /**
1363         * Returns a formatter for a four digit weekyear, two digit week of
1364         * weekyear, and one digit day of week. (xxxx-'W'ww-e)
1365         * 
1366         * @return a formatter for xxxx-'W'ww-e
1367         */
1368        public static DateTimeFormatter weekyearWeekDay() {
1369            if (wwd == null) {
1370                wwd = new DateTimeFormatterBuilder()
1371                    .append(weekyearElement())
1372                    .append(weekElement())
1373                    .append(dayOfWeekElement())
1374                    .toFormatter();
1375            }
1376            return wwd;
1377        }
1378    
1379        /**
1380         * Returns a formatter for a two digit hour of day. (HH)
1381         * 
1382         * @return a formatter for HH
1383         */
1384        public static DateTimeFormatter hour() {
1385            return hourElement();
1386        }
1387    
1388        /**
1389         * Returns a formatter for a two digit hour of day and two digit minute of
1390         * hour. (HH:mm)
1391         * 
1392         * @return a formatter for HH:mm
1393         */
1394        public static DateTimeFormatter hourMinute() {
1395            if (hm == null) {
1396                hm = new DateTimeFormatterBuilder()
1397                    .append(hourElement())
1398                    .append(minuteElement())
1399                    .toFormatter();
1400            }
1401            return hm;
1402        }
1403    
1404        /**
1405         * Returns a formatter for a two digit hour of day, two digit minute of
1406         * hour, and two digit second of minute. (HH:mm:ss)
1407         * 
1408         * @return a formatter for HH:mm:ss
1409         */
1410        public static DateTimeFormatter hourMinuteSecond() {
1411            if (hms == null) {
1412                hms = new DateTimeFormatterBuilder()
1413                    .append(hourElement())
1414                    .append(minuteElement())
1415                    .append(secondElement())
1416                    .toFormatter();
1417            }
1418            return hms;
1419        }
1420    
1421        /**
1422         * Returns a formatter for a two digit hour of day, two digit minute of
1423         * hour, two digit second of minute, and three digit fraction of
1424         * second (HH:mm:ss.SSS). Parsing will parse up to 3 fractional second
1425         * digits.
1426         * 
1427         * @return a formatter for HH:mm:ss.SSS
1428         */
1429        public static DateTimeFormatter hourMinuteSecondMillis() {
1430            if (hmsl == null) {
1431                hmsl = new DateTimeFormatterBuilder()
1432                    .append(hourElement())
1433                    .append(minuteElement())
1434                    .append(secondElement())
1435                    .appendLiteral('.')
1436                    .appendFractionOfSecond(3, 3)
1437                    .toFormatter();
1438            }
1439            return hmsl;
1440        }
1441    
1442        /**
1443         * Returns a formatter for a two digit hour of day, two digit minute of
1444         * hour, two digit second of minute, and three digit fraction of
1445         * second (HH:mm:ss.SSS). Parsing will parse up to 9 fractional second
1446         * digits, throwing away all except the first three.
1447         * 
1448         * @return a formatter for HH:mm:ss.SSS
1449         */
1450        public static DateTimeFormatter hourMinuteSecondFraction() {
1451            if (hmsf == null) {
1452                hmsf = new DateTimeFormatterBuilder()
1453                    .append(hourElement())
1454                    .append(minuteElement())
1455                    .append(secondElement())
1456                    .append(fractionElement())
1457                    .toFormatter();
1458            }
1459            return hmsf;
1460        }
1461    
1462        /**
1463         * Returns a formatter that combines a full date and two digit hour of
1464         * day. (yyyy-MM-dd'T'HH)
1465         * 
1466         * @return a formatter for yyyy-MM-dd'T'HH
1467         */
1468        public static DateTimeFormatter dateHour() {
1469            if (dh == null) {
1470                dh = new DateTimeFormatterBuilder()
1471                    .append(date())
1472                    .append(literalTElement())
1473                    .append(hour())
1474                    .toFormatter();
1475            }
1476            return dh;
1477        }
1478    
1479        /**
1480         * Returns a formatter that combines a full date, two digit hour of day,
1481         * and two digit minute of hour. (yyyy-MM-dd'T'HH:mm)
1482         * 
1483         * @return a formatter for yyyy-MM-dd'T'HH:mm
1484         */
1485        public static DateTimeFormatter dateHourMinute() {
1486            if (dhm == null) {
1487                dhm = new DateTimeFormatterBuilder()
1488                    .append(date())
1489                    .append(literalTElement())
1490                    .append(hourMinute())
1491                    .toFormatter();
1492            }
1493            return dhm;
1494        }
1495    
1496        /**
1497         * Returns a formatter that combines a full date, two digit hour of day,
1498         * two digit minute of hour, and two digit second of
1499         * minute. (yyyy-MM-dd'T'HH:mm:ss)
1500         * 
1501         * @return a formatter for yyyy-MM-dd'T'HH:mm:ss
1502         */
1503        public static DateTimeFormatter dateHourMinuteSecond() {
1504            if (dhms == null) {
1505                dhms = new DateTimeFormatterBuilder()
1506                    .append(date())
1507                    .append(literalTElement())
1508                    .append(hourMinuteSecond())
1509                    .toFormatter();
1510            }
1511            return dhms;
1512        }
1513    
1514        /**
1515         * Returns a formatter that combines a full date, two digit hour of day,
1516         * two digit minute of hour, two digit second of minute, and three digit
1517         * fraction of second (yyyy-MM-dd'T'HH:mm:ss.SSS). Parsing will parse up
1518         * to 3 fractional second digits.
1519         * 
1520         * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS
1521         */
1522        public static DateTimeFormatter dateHourMinuteSecondMillis() {
1523            if (dhmsl == null) {
1524                dhmsl = new DateTimeFormatterBuilder()
1525                    .append(date())
1526                    .append(literalTElement())
1527                    .append(hourMinuteSecondMillis())
1528                    .toFormatter();
1529            }
1530            return dhmsl;
1531        }
1532    
1533        /**
1534         * Returns a formatter that combines a full date, two digit hour of day,
1535         * two digit minute of hour, two digit second of minute, and three digit
1536         * fraction of second (yyyy-MM-dd'T'HH:mm:ss.SSS). Parsing will parse up
1537         * to 9 fractional second digits, throwing away all except the first three.
1538         * 
1539         * @return a formatter for yyyy-MM-dd'T'HH:mm:ss.SSS
1540         */
1541        public static DateTimeFormatter dateHourMinuteSecondFraction() {
1542            if (dhmsf == null) {
1543                dhmsf = new DateTimeFormatterBuilder()
1544                    .append(date())
1545                    .append(literalTElement())
1546                    .append(hourMinuteSecondFraction())
1547                    .toFormatter();
1548            }
1549            return dhmsf;
1550        }
1551    
1552        //-----------------------------------------------------------------------
1553        private static DateTimeFormatter yearElement() {
1554            if (ye == null) {
1555                ye = new DateTimeFormatterBuilder()
1556                    .appendYear(4, 9)
1557                    .toFormatter();
1558            }
1559            return ye;
1560        }
1561    
1562        private static DateTimeFormatter monthElement() {
1563            if (mye == null) {
1564                mye = new DateTimeFormatterBuilder()
1565                    .appendLiteral('-')
1566                    .appendMonthOfYear(2)
1567                    .toFormatter();
1568            }
1569            return mye;
1570        }
1571    
1572        private static DateTimeFormatter dayOfMonthElement() {
1573            if (dme == null) {
1574                dme = new DateTimeFormatterBuilder()
1575                    .appendLiteral('-')
1576                    .appendDayOfMonth(2)
1577                    .toFormatter();
1578            }
1579            return dme;
1580        }
1581    
1582        private static DateTimeFormatter weekyearElement() {
1583            if (we == null) {
1584                we = new DateTimeFormatterBuilder()
1585                    .appendWeekyear(4, 9)
1586                    .toFormatter();
1587            }
1588            return we;
1589        }
1590    
1591        private static DateTimeFormatter weekElement() {
1592            if (wwe == null) {
1593                wwe = new DateTimeFormatterBuilder()
1594                    .appendLiteral("-W")
1595                    .appendWeekOfWeekyear(2)
1596                    .toFormatter();
1597            }
1598            return wwe;
1599        }
1600    
1601        private static DateTimeFormatter dayOfWeekElement() {
1602            if (dwe == null) {
1603                dwe = new DateTimeFormatterBuilder()
1604                    .appendLiteral('-')
1605                    .appendDayOfWeek(1)
1606                    .toFormatter();
1607            }
1608            return dwe;
1609        }
1610    
1611        private static DateTimeFormatter dayOfYearElement() {
1612            if (dye == null) {
1613                dye = new DateTimeFormatterBuilder()
1614                    .appendLiteral('-')
1615                    .appendDayOfYear(3)
1616                    .toFormatter();
1617            }
1618            return dye;
1619        }
1620        
1621        private static DateTimeFormatter literalTElement() {
1622            if (lte == null) {
1623                lte = new DateTimeFormatterBuilder()
1624                    .appendLiteral('T')
1625                    .toFormatter();
1626            }
1627            return lte;
1628        }
1629    
1630        private static DateTimeFormatter hourElement() {
1631            if (hde == null) {
1632                hde = new DateTimeFormatterBuilder()
1633                    .appendHourOfDay(2)
1634                    .toFormatter();
1635            }
1636            return hde;
1637        }
1638    
1639        private static DateTimeFormatter minuteElement() {
1640            if (mhe == null) {
1641                mhe = new DateTimeFormatterBuilder()
1642                    .appendLiteral(':')
1643                    .appendMinuteOfHour(2)
1644                    .toFormatter();
1645            }
1646            return mhe;
1647        }
1648    
1649        private static DateTimeFormatter secondElement() {
1650            if (sme == null) {
1651                sme = new DateTimeFormatterBuilder()
1652                    .appendLiteral(':')
1653                    .appendSecondOfMinute(2)
1654                    .toFormatter();
1655            }
1656            return sme;
1657        }
1658    
1659        private static DateTimeFormatter fractionElement() {
1660            if (fse == null) {
1661                fse = new DateTimeFormatterBuilder()
1662                    .appendLiteral('.')
1663                    // Support parsing up to nanosecond precision even though
1664                    // those extra digits will be dropped.
1665                    .appendFractionOfSecond(3, 9)
1666                    .toFormatter();
1667            }
1668            return fse;
1669        }
1670    
1671        private static DateTimeFormatter offsetElement() {
1672            if (ze == null) {
1673                ze = new DateTimeFormatterBuilder()
1674                    .appendTimeZoneOffset("Z", true, 2, 4)
1675                    .toFormatter();
1676            }
1677            return ze;
1678        }
1679    
1680    }