View Javadoc

1   /*
2    *  Copyright 2001-2009 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.format;
17  
18  import java.util.Collection;
19  import java.util.HashSet;
20  import java.util.Set;
21  
22  import org.joda.time.DateTimeFieldType;
23  
24  /**
25   * Factory that creates instances of DateTimeFormatter for the ISO8601 standard.
26   * <p>
27   * Datetime formatting is performed by the {@link DateTimeFormatter} class.
28   * Three classes provide factory methods to create formatters, and this is one.
29   * The others are {@link DateTimeFormat} and {@link DateTimeFormatterBuilder}.
30   * <p>
31   * ISO8601 is the international standard for data interchange. It defines a
32   * framework, rather than an absolute standard. As a result this provider has a
33   * number of methods that represent common uses of the framework. The most common
34   * formats are {@link #date() date}, {@link #time() time}, and {@link #dateTime() dateTime}.
35   * <p>
36   * For example, to format a date time in ISO format:
37   * <pre>
38   * DateTime dt = new DateTime();
39   * DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
40   * String str = fmt.print(dt);
41   * </pre>
42   * <p>
43   * It is important to understand that these formatters are not linked to
44   * the <code>ISOChronology</code>. These formatters may be used with any
45   * chronology, however there may be certain side effects with more unusual
46   * chronologies. For example, the ISO formatters rely on dayOfWeek being
47   * single digit, dayOfMonth being two digit and dayOfYear being three digit.
48   * A chronology with a ten day week would thus cause issues. However, in
49   * general, it is safe to use these formatters with other chronologies.
50   * <p>
51   * ISODateTimeFormat is thread-safe and immutable, and the formatters it
52   * returns are as well.
53   *
54   * @author Brian S O'Neill
55   * @since 1.0
56   * @see DateTimeFormat
57   * @see DateTimeFormatterBuilder
58   */
59  public class ISODateTimeFormat {
60  
61      //-----------------------------------------------------------------------
62      private static DateTimeFormatter
63          ye,  // year element (yyyy)
64          mye, // monthOfYear element (-MM)
65          dme, // dayOfMonth element (-dd)
66          we,  // weekyear element (xxxx)
67          wwe, // weekOfWeekyear element (-ww)
68          dwe, // dayOfWeek element (-ee)
69          dye, // dayOfYear element (-DDD)
70          hde, // hourOfDay element (HH)
71          mhe, // minuteOfHour element (:mm)
72          sme, // secondOfMinute element (:ss)
73          fse, // fractionOfSecond element (.SSSSSSSSS)
74          ze,  // zone offset element
75          lte, // literal 'T' element
76          
77          //y,   // year (same as year element)
78          ym,  // year month
79          ymd, // year month day
80  
81          //w,   // weekyear (same as weekyear element)
82          ww,  // weekyear week
83          wwd, // weekyear week day
84  
85          //h,    // hour (same as hour element)
86          hm,   // hour minute
87          hms,  // hour minute second
88          hmsl, // hour minute second millis
89          hmsf, // hour minute second fraction
90  
91          dh,    // date hour
92          dhm,   // date hour minute
93          dhms,  // date hour minute second
94          dhmsl, // date hour minute second millis
95          dhmsf, // date hour minute second fraction
96  
97          //d,  // date (same as ymd)
98          t,  // time
99          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 }