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