-
Notifications
You must be signed in to change notification settings - Fork 5.4k
/
DateTimeFormatterBuilder.java
5644 lines (5370 loc) · 248 KB
/
DateTimeFormatterBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* This file is available under and governed by the GNU General Public
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
*
* Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package java.time.format;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
import static java.time.temporal.ChronoField.INSTANT_SECONDS;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
import static java.time.temporal.ChronoField.OFFSET_SECONDS;
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoField.ERA;
import java.lang.ref.SoftReference;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.ParsePosition;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.Chronology;
import java.time.chrono.Era;
import java.time.chrono.IsoChronology;
import java.time.format.DateTimeTextProvider.LocaleStore;
import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields;
import java.time.temporal.JulianFields;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQueries;
import java.time.temporal.TemporalQuery;
import java.time.temporal.ValueRange;
import java.time.temporal.WeekFields;
import java.time.zone.ZoneRulesProvider;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import sun.text.spi.JavaTimeDateTimePatternProvider;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
import sun.util.locale.provider.TimeZoneNameUtility;
/**
* Builder to create date-time formatters.
* <p>
* This allows a {@code DateTimeFormatter} to be created.
* All date-time formatters are created ultimately using this builder.
* <p>
* The basic elements of date-time can all be added:
* <ul>
* <li>Value - a numeric value</li>
* <li>Fraction - a fractional value including the decimal place. Always use this when
* outputting fractions to ensure that the fraction is parsed correctly</li>
* <li>Text - the textual equivalent for the value</li>
* <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
* <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
* <li>ZoneText - the name of the time-zone</li>
* <li>ChronologyId - the {@linkplain Chronology chronology} id</li>
* <li>ChronologyText - the name of the chronology</li>
* <li>Literal - a text literal</li>
* <li>Nested and Optional - formats can be nested or made optional</li>
* </ul>
* In addition, any of the elements may be decorated by padding, either with spaces or any other character.
* <p>
* Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
* can be used, see {@link #appendPattern(String)}.
* In practice, this simply parses the pattern and calls other methods on the builder.
*
* @implSpec
* This class is a mutable builder intended for use from a single thread.
*
* @since 1.8
*/
public final class DateTimeFormatterBuilder {
/**
* Query for a time-zone that is region-only.
*/
private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
ZoneId zone = temporal.query(TemporalQueries.zoneId());
return zone instanceof ZoneOffset ? null : zone;
};
/**
* The currently active builder, used by the outermost builder.
*/
private DateTimeFormatterBuilder active = this;
/**
* The parent builder, null for the outermost builder.
*/
private final DateTimeFormatterBuilder parent;
/**
* The list of printers that will be used.
*/
private final List<DateTimePrinterParser> printerParsers = new ArrayList<>();
/**
* Whether this builder produces an optional formatter.
*/
private final boolean optional;
/**
* The width to pad the next field to.
*/
private int padNextWidth;
/**
* The character to pad the next field with.
*/
private char padNextChar;
/**
* The index of the last variable width value parser.
*/
private int valueParserIndex = -1;
/**
* Gets the formatting pattern for date and time styles for a locale and chronology.
* The locale and chronology are used to lookup the locale specific format
* for the requested dateStyle and/or timeStyle.
* <p>
* If the locale contains the "rg" (region override)
* <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
* the formatting pattern is overridden with the one appropriate for the region.
*
* @param dateStyle the FormatStyle for the date, null for time-only pattern
* @param timeStyle the FormatStyle for the time, null for date-only pattern
* @param chrono the Chronology, non-null
* @param locale the locale, non-null
* @return the locale and Chronology specific formatting pattern
* @throws IllegalArgumentException if both dateStyle and timeStyle are null
*/
public static String getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
Chronology chrono, Locale locale) {
Objects.requireNonNull(locale, "locale");
Objects.requireNonNull(chrono, "chrono");
if (dateStyle == null && timeStyle == null) {
throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null");
}
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale);
JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
return provider.getJavaTimeDateTimePattern(convertStyle(timeStyle),
convertStyle(dateStyle), chrono.getCalendarType(),
CalendarDataUtility.findRegionOverride(locale));
}
/**
* Returns the formatting pattern for the requested template for a locale and chronology.
* The locale and chronology are used to lookup the locale specific format
* for the requested template.
* <p>
* If the locale contains the "rg" (region override)
* <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
* the formatting pattern is overridden with the one appropriate for the region.
* <p>
* Refer to {@link #appendLocalized(String)} for the detail of {@code requestedTemplate}
* argument.
*
* @param requestedTemplate the requested template, not null
* @param chrono the Chronology, non-null
* @param locale the locale, non-null
* @return the locale and Chronology specific formatting pattern
* @throws IllegalArgumentException if {@code requestedTemplate} does not match
* the regular expression syntax described in {@link #appendLocalized(String)}.
* @throws DateTimeException if a match for the localized pattern for
* {@code requestedTemplate} is not available
* @see #appendLocalized(String)
* @since 19
*/
public static String getLocalizedDateTimePattern(String requestedTemplate,
Chronology chrono, Locale locale) {
Objects.requireNonNull(requestedTemplate, "requestedTemplate");
Objects.requireNonNull(chrono, "chrono");
Objects.requireNonNull(locale, "locale");
Locale override = CalendarDataUtility.findRegionOverride(locale);
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, override);
JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
return provider.getJavaTimeDateTimePattern(requestedTemplate,
chrono.getCalendarType(),
override);
}
/**
* Converts the given FormatStyle to the java.text.DateFormat style.
*
* @param style the FormatStyle style
* @return the int style, or -1 if style is null, indicating un-required
*/
private static int convertStyle(FormatStyle style) {
if (style == null) {
return -1;
}
return style.ordinal(); // indices happen to align
}
/**
* Constructs a new instance of the builder.
*/
public DateTimeFormatterBuilder() {
super();
parent = null;
optional = false;
}
/**
* Constructs a new instance of the builder.
*
* @param parent the parent builder, not null
* @param optional whether the formatter is optional, not null
*/
private DateTimeFormatterBuilder(DateTimeFormatterBuilder parent, boolean optional) {
super();
this.parent = parent;
this.optional = optional;
}
//-----------------------------------------------------------------------
/**
* Changes the parse style to be case sensitive for the remainder of the formatter.
* <p>
* Parsing can be case sensitive or insensitive - by default it is case sensitive.
* This method allows the case sensitivity setting of parsing to be changed.
* <p>
* Calling this method changes the state of the builder such that all
* subsequent builder method calls will parse text in case sensitive mode.
* See {@link #parseCaseInsensitive} for the opposite setting.
* The parse case sensitive/insensitive methods may be called at any point
* in the builder, thus the parser can swap between case parsing modes
* multiple times during the parse.
* <p>
* Since the default is case sensitive, this method should only be used after
* a previous call to {@code #parseCaseInsensitive}.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder parseCaseSensitive() {
appendInternal(SettingsParser.SENSITIVE);
return this;
}
/**
* Changes the parse style to be case insensitive for the remainder of the formatter.
* <p>
* Parsing can be case sensitive or insensitive - by default it is case sensitive.
* This method allows the case sensitivity setting of parsing to be changed.
* <p>
* Calling this method changes the state of the builder such that all
* subsequent builder method calls will parse text in case insensitive mode.
* See {@link #parseCaseSensitive()} for the opposite setting.
* The parse case sensitive/insensitive methods may be called at any point
* in the builder, thus the parser can swap between case parsing modes
* multiple times during the parse.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder parseCaseInsensitive() {
appendInternal(SettingsParser.INSENSITIVE);
return this;
}
//-----------------------------------------------------------------------
/**
* Changes the parse style to be strict for the remainder of the formatter.
* <p>
* Parsing can be strict or lenient - by default it is strict.
* This controls the degree of flexibility in matching the text and sign styles.
* <p>
* When used, this method changes the parsing to be strict from this point onwards.
* As strict is the default, this is normally only needed after calling {@link #parseLenient()}.
* The change will remain in force until the end of the formatter that is eventually
* constructed or until {@code parseLenient} is called.
*
* @implSpec A {@link Character#SPACE_SEPARATOR SPACE_SEPARATOR} in the input
* text will not match any other {@link Character#SPACE_SEPARATOR SPACE_SEPARATOR}s
* in the pattern with the strict parse style.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder parseStrict() {
appendInternal(SettingsParser.STRICT);
return this;
}
/**
* Changes the parse style to be lenient for the remainder of the formatter.
* Note that case sensitivity is set separately to this method.
* <p>
* Parsing can be strict or lenient - by default it is strict.
* This controls the degree of flexibility in matching the text and sign styles.
* Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
* <p>
* When used, this method changes the parsing to be lenient from this point onwards.
* The change will remain in force until the end of the formatter that is eventually
* constructed or until {@code parseStrict} is called.
*
* @implSpec A {@link Character#SPACE_SEPARATOR SPACE_SEPARATOR} in the input
* text will match any other {@link Character#SPACE_SEPARATOR SPACE_SEPARATOR}s
* in the pattern with the lenient parse style.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder parseLenient() {
appendInternal(SettingsParser.LENIENT);
return this;
}
//-----------------------------------------------------------------------
/**
* Appends a default value for a field to the formatter for use in parsing.
* <p>
* This appends an instruction to the builder to inject a default value
* into the parsed result. This is especially useful in conjunction with
* optional parts of the formatter.
* <p>
* For example, consider a formatter that parses the year, followed by
* an optional month, with a further optional day-of-month. Using such a
* formatter would require the calling code to check whether a full date,
* year-month or just a year had been parsed. This method can be used to
* default the month and day-of-month to a sensible value, such as the
* first of the month, allowing the calling code to always get a date.
* <p>
* During formatting, this method has no effect.
* <p>
* During parsing, the current state of the parse is inspected.
* If the specified field has no associated value, because it has not been
* parsed successfully at that point, then the specified value is injected
* into the parse result. Injection is immediate, thus the field-value pair
* will be visible to any subsequent elements in the formatter.
* As such, this method is normally called at the end of the builder.
*
* @param field the field to default the value of, not null
* @param value the value to default the field to
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
Objects.requireNonNull(field, "field");
appendInternal(new DefaultValueParser(field, value));
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the value of a date-time field to the formatter using a normal
* output style.
* <p>
* The value of the field will be output during a format.
* If the value cannot be obtained then an exception will be thrown.
* <p>
* The value will be printed as per the normal format of an integer value.
* Only negative numbers will be signed. No padding will be added.
* <p>
* The parser for a variable width value such as this normally behaves greedily,
* requiring one digit, but accepting as many digits as possible.
* This behavior can be affected by 'adjacent value parsing'.
* See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
*
* @param field the field to append, not null
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendValue(TemporalField field) {
Objects.requireNonNull(field, "field");
appendValue(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
return this;
}
/**
* Appends the value of a date-time field to the formatter using a fixed
* width, zero-padded approach.
* <p>
* The value of the field will be output during a format.
* If the value cannot be obtained then an exception will be thrown.
* <p>
* The value will be zero-padded on the left. If the size of the value
* means that it cannot be printed within the width then an exception is thrown.
* If the value of the field is negative then an exception is thrown during formatting.
* <p>
* This method supports a special technique of parsing known as 'adjacent value parsing'.
* This technique solves the problem where a value, variable or fixed width, is followed by one or more
* fixed length values. The standard parser is greedy, and thus it would normally
* steal the digits that are needed by the fixed width value parsers that follow the
* variable width one.
* <p>
* No action is required to initiate 'adjacent value parsing'.
* When a call to {@code appendValue} is made, the builder
* enters adjacent value parsing setup mode. If the immediately subsequent method
* call or calls on the same builder are for a fixed width value, then the parser will reserve
* space so that the fixed width values can be parsed.
* <p>
* For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);}
* The year is a variable width parse of between 1 and 19 digits.
* The month is a fixed width parse of 2 digits.
* Because these were appended to the same builder immediately after one another,
* the year parser will reserve two digits for the month to parse.
* Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6.
* Without adjacent value parsing, the year would greedily parse all six digits and leave
* nothing for the month.
* <p>
* Adjacent value parsing applies to each set of fixed width not-negative values in the parser
* that immediately follow any kind of value, variable or fixed width.
* Calling any other append method will end the setup of adjacent value parsing.
* Thus, in the unlikely event that you need to avoid adjacent value parsing behavior,
* simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder}
* and add that to this builder.
* <p>
* If adjacent parsing is active, then parsing must match exactly the specified
* number of digits in both strict and lenient modes.
* In addition, no positive or negative sign is permitted.
*
* @param field the field to append, not null
* @param width the width of the printed field, from 1 to 19
* @return this, for chaining, not null
* @throws IllegalArgumentException if the width is invalid
*/
public DateTimeFormatterBuilder appendValue(TemporalField field, int width) {
Objects.requireNonNull(field, "field");
if (width < 1 || width > 19) {
throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width);
}
NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE);
appendValue(pp);
return this;
}
/**
* Appends the value of a date-time field to the formatter providing full
* control over formatting.
* <p>
* The value of the field will be output during a format.
* If the value cannot be obtained then an exception will be thrown.
* <p>
* This method provides full control of the numeric formatting, including
* zero-padding and the positive/negative sign.
* <p>
* The parser for a variable width value such as this normally behaves greedily,
* accepting as many digits as possible.
* This behavior can be affected by 'adjacent value parsing'.
* See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
* <p>
* In strict parsing mode, the minimum number of parsed digits is {@code minWidth}
* and the maximum is {@code maxWidth}.
* In lenient parsing mode, the minimum number of parsed digits is one
* and the maximum is 19 (except as limited by adjacent value parsing).
* <p>
* If this method is invoked with equal minimum and maximum widths and a sign style of
* {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}.
* In this scenario, the formatting and parsing behavior described there occur.
*
* @param field the field to append, not null
* @param minWidth the minimum field width of the printed field, from 1 to 19
* @param maxWidth the maximum field width of the printed field, from 1 to 19
* @param signStyle the positive/negative output style, not null
* @return this, for chaining, not null
* @throws IllegalArgumentException if the widths are invalid
*/
public DateTimeFormatterBuilder appendValue(
TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
return appendValue(field, maxWidth);
}
Objects.requireNonNull(field, "field");
Objects.requireNonNull(signStyle, "signStyle");
if (minWidth < 1 || minWidth > 19) {
throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth);
}
if (maxWidth < 1 || maxWidth > 19) {
throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth);
}
if (maxWidth < minWidth) {
throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " +
maxWidth + " < " + minWidth);
}
NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
appendValue(pp);
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the reduced value of a date-time field to the formatter.
* <p>
* Since fields such as year vary by chronology, it is recommended to use the
* {@link #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date}
* variant of this method in most cases. This variant is suitable for
* simple fields or working with only the ISO chronology.
* <p>
* For formatting, the {@code width} and {@code maxWidth} are used to
* determine the number of characters to format.
* If they are equal then the format is fixed width.
* If the value of the field is within the range of the {@code baseValue} using
* {@code width} characters then the reduced value is formatted otherwise the value is
* truncated to fit {@code maxWidth}.
* The rightmost characters are output to match the width, left padding with zero.
* <p>
* For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
* For lenient parsing, the number of characters must be at least 1 and less than 10.
* If the number of digits parsed is equal to {@code width} and the value is positive,
* the value of the field is computed to be the first number greater than
* or equal to the {@code baseValue} with the same least significant characters,
* otherwise the value parsed is the field value.
* This allows a reduced value to be entered for values in range of the baseValue
* and width and absolute values can be entered for values outside the range.
* <p>
* For example, a base value of {@code 1980} and a width of {@code 2} will have
* valid values from {@code 1980} to {@code 2079}.
* During parsing, the text {@code "12"} will result in the value {@code 2012} as that
* is the value within the range where the last two characters are "12".
* By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}.
*
* @param field the field to append, not null
* @param width the field width of the printed and parsed field, from 1 to 10
* @param maxWidth the maximum field width of the printed field, from 1 to 10
* @param baseValue the base value of the range of valid values
* @return this, for chaining, not null
* @throws IllegalArgumentException if the width or base value is invalid
*/
public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
int width, int maxWidth, int baseValue) {
Objects.requireNonNull(field, "field");
ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, baseValue, null);
appendValue(pp);
return this;
}
/**
* Appends the reduced value of a date-time field to the formatter.
* <p>
* This is typically used for formatting and parsing a two digit year.
* <p>
* The base date is used to calculate the full value during parsing.
* For example, if the base date is 1950-01-01 then parsed values for
* a two digit year parse will be in the range 1950-01-01 to 2049-12-31.
* Only the year would be extracted from the date, thus a base date of
* 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31.
* This behavior is necessary to support fields such as week-based-year
* or other calendar systems where the parsed value does not align with
* standard ISO years.
* <p>
* The exact behavior is as follows. Parse the full set of fields and
* determine the effective chronology using the last chronology if
* it appears more than once. Then convert the base date to the
* effective chronology. Then extract the specified field from the
* chronology-specific base date and use it to determine the
* {@code baseValue} used below.
* <p>
* For formatting, the {@code width} and {@code maxWidth} are used to
* determine the number of characters to format.
* If they are equal then the format is fixed width.
* If the value of the field is within the range of the {@code baseValue} using
* {@code width} characters then the reduced value is formatted otherwise the value is
* truncated to fit {@code maxWidth}.
* The rightmost characters are output to match the width, left padding with zero.
* <p>
* For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
* For lenient parsing, the number of characters must be at least 1 and less than 10.
* If the number of digits parsed is equal to {@code width} and the value is positive,
* the value of the field is computed to be the first number greater than
* or equal to the {@code baseValue} with the same least significant characters,
* otherwise the value parsed is the field value.
* This allows a reduced value to be entered for values in range of the baseValue
* and width and absolute values can be entered for values outside the range.
* <p>
* For example, a base value of {@code 1980} and a width of {@code 2} will have
* valid values from {@code 1980} to {@code 2079}.
* During parsing, the text {@code "12"} will result in the value {@code 2012} as that
* is the value within the range where the last two characters are "12".
* By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}.
*
* @param field the field to append, not null
* @param width the field width of the printed and parsed field, from 1 to 10
* @param maxWidth the maximum field width of the printed field, from 1 to 10
* @param baseDate the base date used to calculate the base value for the range
* of valid values in the parsed chronology, not null
* @return this, for chaining, not null
* @throws IllegalArgumentException if the width or base value is invalid
*/
public DateTimeFormatterBuilder appendValueReduced(
TemporalField field, int width, int maxWidth, ChronoLocalDate baseDate) {
Objects.requireNonNull(field, "field");
Objects.requireNonNull(baseDate, "baseDate");
ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, 0, baseDate);
appendValue(pp);
return this;
}
/**
* Appends a fixed or variable width printer-parser handling adjacent value mode.
* If a PrinterParser is not active then the new PrinterParser becomes
* the active PrinterParser.
* Otherwise, the active PrinterParser is modified depending on the new PrinterParser.
* If the new PrinterParser is fixed width and has sign style {@code NOT_NEGATIVE}
* then its width is added to the active PP and
* the new PrinterParser is forced to be fixed width.
* If the new PrinterParser is variable width, the active PrinterParser is changed
* to be fixed width and the new PrinterParser becomes the active PP.
*
* @param pp the printer-parser, not null
* @return this, for chaining, not null
*/
private DateTimeFormatterBuilder appendValue(NumberPrinterParser pp) {
if (active.valueParserIndex >= 0) {
final int activeValueParser = active.valueParserIndex;
// adjacent parsing mode, update setting in previous parsers
NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(activeValueParser);
if (pp.minWidth == pp.maxWidth && pp.signStyle == SignStyle.NOT_NEGATIVE) {
// Append the width to the subsequentWidth of the active parser
basePP = basePP.withSubsequentWidth(pp.maxWidth);
// Append the new parser as a fixed width
appendInternal(pp.withFixedWidth());
// Retain the previous active parser
active.valueParserIndex = activeValueParser;
} else {
// Modify the active parser to be fixed width
basePP = basePP.withFixedWidth();
// The new parser becomes the mew active parser
active.valueParserIndex = appendInternal(pp);
}
// Replace the modified parser with the updated one
active.printerParsers.set(activeValueParser, basePP);
} else {
// The new Parser becomes the active parser
active.valueParserIndex = appendInternal(pp);
}
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the fractional value of a date-time field to the formatter.
* <p>
* The fractional value of the field will be output including the
* preceding decimal point. The preceding value is not output.
* For example, the second-of-minute value of 15 would be output as {@code .25}.
* <p>
* The width of the printed fraction can be controlled. Setting the
* minimum width to zero will cause no output to be generated.
* The printed fraction will have the minimum width necessary between
* the minimum and maximum widths - trailing zeroes are omitted.
* No rounding occurs due to the maximum width - digits are simply dropped.
* <p>
* When parsing in strict mode, the number of parsed digits must be between
* the minimum and maximum width. In strict mode, if the minimum and maximum widths
* are equal and there is no decimal point then the parser will
* participate in adjacent value parsing, see
* {@link #appendValue(java.time.temporal.TemporalField, int)}. When parsing in lenient mode,
* the minimum width is considered to be zero and the maximum is nine.
* <p>
* If the value cannot be obtained then an exception will be thrown.
* If the value is negative an exception will be thrown.
* If the field does not have a fixed set of valid values then an
* exception will be thrown.
* If the field value in the date-time to be printed is outside the
* range of valid values then an exception will be thrown.
*
* @param field the field to append, not null
* @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9
* @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9
* @param decimalPoint whether to output the localized decimal point symbol
* @return this, for chaining, not null
* @throws IllegalArgumentException if the field has a variable set of valid values or
* either width is invalid
*/
public DateTimeFormatterBuilder appendFraction(
TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
if (field == NANO_OF_SECOND) {
if (minWidth == maxWidth && decimalPoint == false) {
// adjacent parsing
appendValue(new NanosPrinterParser(minWidth, maxWidth, decimalPoint));
} else {
appendInternal(new NanosPrinterParser(minWidth, maxWidth, decimalPoint));
}
} else {
if (minWidth == maxWidth && decimalPoint == false) {
// adjacent parsing
appendValue(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
} else {
appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
}
}
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the text of a date-time field to the formatter using the full
* text style.
* <p>
* The text of the field will be output during a format.
* The value must be within the valid range of the field.
* If the value cannot be obtained then an exception will be thrown.
* If the field has no textual representation, then the numeric value will be used.
* <p>
* The value will be printed as per the normal format of an integer value.
* Only negative numbers will be signed. No padding will be added.
*
* @param field the field to append, not null
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendText(TemporalField field) {
return appendText(field, TextStyle.FULL);
}
/**
* Appends the text of a date-time field to the formatter.
* <p>
* The text of the field will be output during a format.
* The value must be within the valid range of the field.
* If the value cannot be obtained then an exception will be thrown.
* If the field has no textual representation, then the numeric value will be used.
* <p>
* The value will be printed as per the normal format of an integer value.
* Only negative numbers will be signed. No padding will be added.
*
* @param field the field to append, not null
* @param textStyle the text style to use, not null
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) {
Objects.requireNonNull(field, "field");
Objects.requireNonNull(textStyle, "textStyle");
appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance()));
return this;
}
/**
* Appends the text of a date-time field to the formatter using the specified
* map to supply the text.
* <p>
* The standard text outputting methods use the localized text in the JDK.
* This method allows that text to be specified directly.
* The supplied map is not validated by the builder to ensure that formatting or
* parsing is possible, thus an invalid map may throw an error during later use.
* <p>
* Supplying the map of text provides considerable flexibility in formatting and parsing.
* For example, a legacy application might require or supply the months of the
* year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text
* for localized month names. Using this method, a map can be created which
* defines the connection between each value and the text:
* <pre>
* Map<Long, String> map = new HashMap<>();
* map.put(1L, "JNY");
* map.put(2L, "FBY");
* map.put(3L, "MCH");
* ...
* builder.appendText(MONTH_OF_YEAR, map);
* </pre>
* <p>
* Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd",
* or as Roman numerals "I", "II", "III", "IV".
* <p>
* During formatting, the value is obtained and checked that it is in the valid range.
* If text is not available for the value then it is output as a number.
* During parsing, the parser will match against the map of text and numeric values.
*
* @param field the field to append, not null
* @param textLookup the map from the value to the text
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendText(TemporalField field, Map<Long, String> textLookup) {
Objects.requireNonNull(field, "field");
Objects.requireNonNull(textLookup, "textLookup");
Map<Long, String> copy = new LinkedHashMap<>(textLookup);
Map<TextStyle, Map<Long, String>> map = Collections.singletonMap(TextStyle.FULL, copy);
final LocaleStore store = new LocaleStore(map);
DateTimeTextProvider provider = new DateTimeTextProvider() {
@Override
public String getText(Chronology chrono, TemporalField field,
long value, TextStyle style, Locale locale) {
return store.getText(value, style);
}
@Override
public String getText(TemporalField field, long value, TextStyle style, Locale locale) {
return store.getText(value, style);
}
@Override
public Iterator<Entry<String, Long>> getTextIterator(Chronology chrono,
TemporalField field, TextStyle style, Locale locale) {
return store.getTextIterator(style);
}
@Override
public Iterator<Entry<String, Long>> getTextIterator(TemporalField field,
TextStyle style, Locale locale) {
return store.getTextIterator(style);
}
};
appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider));
return this;
}
//-----------------------------------------------------------------------
/**
* Appends an instant using ISO-8601 to the formatter, formatting fractional
* digits in groups of three.
* <p>
* Instants have a fixed output format.
* They are converted to a date-time with a zone-offset of UTC and formatted
* using the standard ISO-8601 format.
* With this method, formatting nano-of-second outputs zero, three, six
* or nine digits as necessary.
* The localized decimal style is not used.
* <p>
* The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
* and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS}
* may be outside the maximum range of {@code LocalDateTime}.
* <p>
* The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
* The end-of-day time of '24:00' is handled as midnight at the start of the following day.
* The leap-second time of '23:59:59' is handled to some degree, see
* {@link DateTimeFormatter#parsedLeapSecond()} for full details.
* <p>
* When formatting, the instant will always be suffixed by 'Z' to indicate UTC.
* When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()}
* will be used to parse the offset, converting the instant to UTC as necessary.
* <p>
* An alternative to this method is to format/parse the instant as a single
* epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendInstant() {
appendInternal(new InstantPrinterParser(-2));
return this;
}
/**
* Appends an instant using ISO-8601 to the formatter with control over
* the number of fractional digits.
* <p>
* Instants have a fixed output format, although this method provides some
* control over the fractional digits. They are converted to a date-time
* with a zone-offset of UTC and printed using the standard ISO-8601 format.
* The localized decimal style is not used.
* <p>
* The {@code fractionalDigits} parameter allows the output of the fractional
* second to be controlled. Specifying zero will cause no fractional digits
* to be output. From 1 to 9 will output an increasing number of digits, using
* zero right-padding if necessary. The special value -1 is used to output as
* many digits as necessary to avoid any trailing zeroes.
* <p>
* When parsing in strict mode, the number of parsed digits must match the
* fractional digits. When parsing in lenient mode, any number of fractional
* digits from zero to nine are accepted.
* <p>
* The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
* and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS}
* may be outside the maximum range of {@code LocalDateTime}.
* <p>
* The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
* The end-of-day time of '24:00' is handled as midnight at the start of the following day.
* The leap-second time of '23:59:60' is handled to some degree, see
* {@link DateTimeFormatter#parsedLeapSecond()} for full details.
* <p>
* An alternative to this method is to format/parse the instant as a single
* epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
*
* @param fractionalDigits the number of fractional second digits to format with,
* from 0 to 9, or -1 to use as many digits as necessary
* @return this, for chaining, not null
* @throws IllegalArgumentException if the number of fractional digits is invalid
*/
public DateTimeFormatterBuilder appendInstant(int fractionalDigits) {
if (fractionalDigits < -1 || fractionalDigits > 9) {
throw new IllegalArgumentException("The fractional digits must be from -1 to 9 inclusive but was " + fractionalDigits);
}
appendInternal(new InstantPrinterParser(fractionalDigits));
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the zone offset, such as '+01:00', to the formatter.
* <p>
* This appends an instruction to format/parse the offset ID to the builder.
* This is equivalent to calling {@code appendOffset("+HH:mm:ss", "Z")}.
* See {@link #appendOffset(String, String)} for details on formatting
* and parsing.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendOffsetId() {
appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
return this;
}
/**
* Appends the zone offset, such as '+01:00', to the formatter.
* <p>
* This appends an instruction to format/parse the offset ID to the builder.
* <p>
* During formatting, the offset is obtained using a mechanism equivalent
* to querying the temporal with {@link TemporalQueries#offset()}.
* It will be printed using the format defined below.
* If the offset cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* When parsing in strict mode, the input must contain the mandatory
* and optional elements are defined by the specified pattern.
* If the offset cannot be parsed then an exception is thrown unless
* the section of the formatter is optional.
* <p>
* When parsing in lenient mode, only the hours are mandatory - minutes
* and seconds are optional. The colons are required if the specified
* pattern contains a colon. If the specified pattern is "+HH", the
* presence of colons is determined by whether the character after the
* hour digits is a colon or not.
* If the offset cannot be parsed then an exception is thrown unless
* the section of the formatter is optional.
* <p>
* The format of the offset is controlled by a pattern which must be one
* of the following:
* <ul>
* <li>{@code +HH} - hour only, ignoring minute and second
* <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
* <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
* <li>{@code +HHMM} - hour and minute, ignoring second, no colon
* <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
* <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon