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 */
016package org.joda.time.format;
017
018import java.util.Collection;
019import java.util.HashSet;
020import java.util.Set;
021
022import 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 */
059public 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}