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 }