66 import static java.time.temporal.ChronoField.INSTANT_SECONDS;
67 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
68 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
69 import static java.time.temporal.ChronoField.NANO_OF_SECOND;
70 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
71 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
72 import static java.time.temporal.ChronoField.YEAR;
73
74 import java.lang.ref.SoftReference;
75 import java.math.BigDecimal;
76 import java.math.BigInteger;
77 import java.math.RoundingMode;
78 import java.text.ParsePosition;
79 import java.time.DateTimeException;
80 import java.time.Instant;
81 import java.time.LocalDateTime;
82 import java.time.ZoneId;
83 import java.time.ZoneOffset;
84 import java.time.chrono.Chronology;
85 import java.time.chrono.IsoChronology;
86 import java.time.chrono.JapaneseChronology;
87 import java.time.format.DateTimeTextProvider.LocaleStore;
88 import java.time.temporal.ChronoField;
89 import java.time.temporal.IsoFields;
90 import java.time.temporal.Queries;
91 import java.time.temporal.TemporalAccessor;
92 import java.time.temporal.TemporalField;
93 import java.time.temporal.TemporalQuery;
94 import java.time.temporal.ValueRange;
95 import java.time.temporal.WeekFields;
96 import java.time.zone.ZoneRulesProvider;
97 import java.util.AbstractMap.SimpleImmutableEntry;
98 import java.util.ArrayList;
99 import java.util.Arrays;
100 import java.util.Collections;
101 import java.util.Comparator;
102 import java.util.HashMap;
103 import java.util.HashSet;
104 import java.util.Iterator;
105 import java.util.LinkedHashMap;
106 import java.util.List;
107 import java.util.Locale;
108 import java.util.Map;
109 import java.util.Map.Entry;
110 import java.util.Objects;
111 import java.util.Set;
112 import java.util.TimeZone;
113 import java.util.concurrent.ConcurrentHashMap;
114
115 import sun.util.locale.provider.TimeZoneNameUtility;
116
117 /**
118 * Builder to create date-time formatters.
119 * <p>
120 * This allows a {@code DateTimeFormatter} to be created.
121 * All date-time formatters are created ultimately using this builder.
122 * <p>
123 * The basic elements of date-time can all be added:
124 * <p><ul>
125 * <li>Value - a numeric value</li>
126 * <li>Fraction - a fractional value including the decimal place. Always use this when
127 * outputting fractions to ensure that the fraction is parsed correctly</li>
128 * <li>Text - the textual equivalent for the value</li>
129 * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
130 * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
131 * <li>ZoneText - the name of the time-zone</li>
132 * <li>Literal - a text literal</li>
133 * <li>Nested and Optional - formats can be nested or made optional</li>
134 * <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li>
135 * </ul><p>
136 * In addition, any of the elements may be decorated by padding, either with spaces or any other character.
137 * <p>
138 * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
139 * can be used, see {@link #appendPattern(String)}.
140 * In practice, this simply parses the pattern and calls other methods on the builder.
141 *
142 * <h3>Specification for implementors</h3>
143 * This class is a mutable builder intended for use from a single thread.
144 *
145 * @since 1.8
146 */
147 public final class DateTimeFormatterBuilder {
148
149 /**
150 * Query for a time-zone that is region-only.
151 */
152 private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
153 ZoneId zone = temporal.query(Queries.zoneId());
154 return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
155 };
156
157 /**
158 * The currently active builder, used by the outermost builder.
159 */
160 private DateTimeFormatterBuilder active = this;
161 /**
162 * The parent builder, null for the outermost builder.
163 */
164 private final DateTimeFormatterBuilder parent;
165 /**
166 * The list of printers that will be used.
167 */
168 private final List<DateTimePrinterParser> printerParsers = new ArrayList<>();
169 /**
170 * Whether this builder produces an optional formatter.
171 */
172 private final boolean optional;
173 /**
271 * Changes the parse style to be lenient for the remainder of the formatter.
272 * Note that case sensitivity is set separately to this method.
273 * <p>
274 * Parsing can be strict or lenient - by default its strict.
275 * This controls the degree of flexibility in matching the text and sign styles.
276 * Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
277 * <p>
278 * When used, this method changes the parsing to be lenient from this point onwards.
279 * The change will remain in force until the end of the formatter that is eventually
280 * constructed or until {@code parseStrict} is called.
281 *
282 * @return this, for chaining, not null
283 */
284 public DateTimeFormatterBuilder parseLenient() {
285 appendInternal(SettingsParser.LENIENT);
286 return this;
287 }
288
289 //-----------------------------------------------------------------------
290 /**
291 * Appends the value of a date-time field to the formatter using a normal
292 * output style.
293 * <p>
294 * The value of the field will be output during a format.
295 * If the value cannot be obtained then an exception will be thrown.
296 * <p>
297 * The value will be printed as per the normal format of an integer value.
298 * Only negative numbers will be signed. No padding will be added.
299 * <p>
300 * The parser for a variable width value such as this normally behaves greedily,
301 * requiring one digit, but accepting as many digits as possible.
302 * This behavior can be affected by 'adjacent value parsing'.
303 * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
304 *
305 * @param field the field to append, not null
306 * @return this, for chaining, not null
307 */
308 public DateTimeFormatterBuilder appendValue(TemporalField field) {
309 Objects.requireNonNull(field, "field");
310 active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
638
639 /**
640 * Appends the zone offset, such as '+01:00', to the formatter.
641 * <p>
642 * This appends an instruction to format/parse the offset ID to the builder.
643 * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}.
644 *
645 * @return this, for chaining, not null
646 */
647 public DateTimeFormatterBuilder appendOffsetId() {
648 appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
649 return this;
650 }
651
652 /**
653 * Appends the zone offset, such as '+01:00', to the formatter.
654 * <p>
655 * This appends an instruction to format/parse the offset ID to the builder.
656 * <p>
657 * During formatting, the offset is obtained using a mechanism equivalent
658 * to querying the temporal with {@link Queries#offset()}.
659 * It will be printed using the format defined below.
660 * If the offset cannot be obtained then an exception is thrown unless the
661 * section of the formatter is optional.
662 * <p>
663 * During parsing, the offset is parsed using the format defined below.
664 * If the offset cannot be parsed then an exception is thrown unless the
665 * section of the formatter is optional.
666 * <p>
667 * The format of the offset is controlled by a pattern which must be one
668 * of the following:
669 * <p><ul>
670 * <li>{@code +HH} - hour only, ignoring minute and second
671 * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
672 * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
673 * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
674 * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
675 * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
676 * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
677 * <li>{@code +HHMMSS} - hour, minute and second, no colon
678 * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
679 * </ul><p>
680 * The "no offset" text controls what text is printed when the total amount of
681 * the offset fields to be output is zero.
682 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
683 * Three formats are accepted for parsing UTC - the "no offset" text, and the
684 * plus and minus versions of zero defined by the pattern.
685 *
686 * @param pattern the pattern to use, not null
687 * @param noOffsetText the text to use when the offset is zero, not null
688 * @return this, for chaining, not null
689 */
690 public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
691 appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
692 return this;
693 }
694
695 //-----------------------------------------------------------------------
696 /**
697 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
698 * <p>
699 * This appends an instruction to format/parse the zone ID to the builder.
700 * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
701 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable
702 * for use with this method, see {@link #appendZoneOrOffsetId()}.
703 * <p>
704 * During formatting, the zone is obtained using a mechanism equivalent
705 * to querying the temporal with {@link Queries#zoneId()}.
706 * It will be printed using the result of {@link ZoneId#getId()}.
707 * If the zone cannot be obtained then an exception is thrown unless the
708 * section of the formatter is optional.
709 * <p>
710 * During parsing, the text must match a known zone or offset.
711 * There are two types of zone ID, offset-based, such as '+01:30' and
712 * region-based, such as 'Europe/London'. These are parsed differently.
713 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
714 * expects an offset-based zone and will not match region-based zones.
715 * The offset ID, such as '+02:30', may be at the start of the parse,
716 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
717 * equivalent to using {@link #appendOffset(String, String)} using the
718 * arguments 'HH:MM:ss' and the no offset string '0'.
719 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
720 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
721 * In all other cases, the list of known region-based zones is used to
722 * find the longest available match. If no match is found, and the parse
723 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
724 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
725 * <p>
726 * For example, the following will parse:
727 * <pre>
728 * "Europe/London" -> ZoneId.of("Europe/London")
729 * "Z" -> ZoneOffset.UTC
730 * "UT" -> ZoneOffset.UTC
731 * "UTC" -> ZoneOffset.UTC
732 * "GMT" -> ZoneOffset.UTC
733 * "UT0" -> ZoneOffset.UTC
734 * "UTC0" -> ZoneOffset.UTC
735 * "GMT0" -> ZoneOffset.UTC
736 * "+01:30" -> ZoneOffset.of("+01:30")
737 * "UT+01:30" -> ZoneOffset.of("+01:30")
738 * "UTC+01:30" -> ZoneOffset.of("+01:30")
739 * "GMT+01:30" -> ZoneOffset.of("+01:30")
740 * </pre>
741 *
742 * @return this, for chaining, not null
743 * @see #appendZoneRegionId()
744 */
745 public DateTimeFormatterBuilder appendZoneId() {
746 appendInternal(new ZoneIdPrinterParser(Queries.zoneId(), "ZoneId()"));
747 return this;
748 }
749
750 /**
751 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
752 * rejecting the zone ID if it is a {@code ZoneOffset}.
753 * <p>
754 * This appends an instruction to format/parse the zone ID to the builder
755 * only if it is a region-based ID.
756 * <p>
757 * During formatting, the zone is obtained using a mechanism equivalent
758 * to querying the temporal with {@link Queries#zoneId()}.
759 * If the zone is a {@code ZoneOffset} or it cannot be obtained then
760 * an exception is thrown unless the section of the formatter is optional.
761 * If the zone is not an offset, then the zone will be printed using
762 * the zone ID from {@link ZoneId#getId()}.
763 * <p>
764 * During parsing, the text must match a known zone or offset.
765 * There are two types of zone ID, offset-based, such as '+01:30' and
766 * region-based, such as 'Europe/London'. These are parsed differently.
767 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
768 * expects an offset-based zone and will not match region-based zones.
769 * The offset ID, such as '+02:30', may be at the start of the parse,
770 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
771 * equivalent to using {@link #appendOffset(String, String)} using the
772 * arguments 'HH:MM:ss' and the no offset string '0'.
773 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
774 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
775 * In all other cases, the list of known region-based zones is used to
776 * find the longest available match. If no match is found, and the parse
777 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
778 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
779 * <p>
780 * For example, the following will parse:
781 * <pre>
782 * "Europe/London" -> ZoneId.of("Europe/London")
783 * "Z" -> ZoneOffset.UTC
784 * "UT" -> ZoneOffset.UTC
785 * "UTC" -> ZoneOffset.UTC
786 * "GMT" -> ZoneOffset.UTC
787 * "UT0" -> ZoneOffset.UTC
788 * "UTC0" -> ZoneOffset.UTC
789 * "GMT0" -> ZoneOffset.UTC
790 * "+01:30" -> ZoneOffset.of("+01:30")
791 * "UT+01:30" -> ZoneOffset.of("+01:30")
792 * "UTC+01:30" -> ZoneOffset.of("+01:30")
793 * "GMT+01:30" -> ZoneOffset.of("+01:30")
794 * </pre>
795 * <p>
796 * Note that this method is is identical to {@code appendZoneId()} except
797 * in the mechanism used to obtain the zone.
798 * Note also that parsing accepts offsets, whereas formatting will never
799 * produce one.
800 *
801 * @return this, for chaining, not null
802 * @see #appendZoneId()
803 */
804 public DateTimeFormatterBuilder appendZoneRegionId() {
805 appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()"));
806 return this;
807 }
808
809 /**
810 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to
811 * the formatter, using the best available zone ID.
812 * <p>
813 * This appends an instruction to format/parse the best available
814 * zone or offset ID to the builder.
815 * The zone ID is obtained in a lenient manner that first attempts to
816 * find a true zone ID, such as that on {@code ZonedDateTime}, and
817 * then attempts to find an offset, such as that on {@code OffsetDateTime}.
818 * <p>
819 * During formatting, the zone is obtained using a mechanism equivalent
820 * to querying the temporal with {@link Queries#zone()}.
821 * It will be printed using the result of {@link ZoneId#getId()}.
822 * If the zone cannot be obtained then an exception is thrown unless the
823 * section of the formatter is optional.
824 * <p>
825 * During parsing, the text must match a known zone or offset.
826 * There are two types of zone ID, offset-based, such as '+01:30' and
827 * region-based, such as 'Europe/London'. These are parsed differently.
828 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
829 * expects an offset-based zone and will not match region-based zones.
830 * The offset ID, such as '+02:30', may be at the start of the parse,
831 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
832 * equivalent to using {@link #appendOffset(String, String)} using the
833 * arguments 'HH:MM:ss' and the no offset string '0'.
834 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
835 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
836 * In all other cases, the list of known region-based zones is used to
837 * find the longest available match. If no match is found, and the parse
838 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
839 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
840 * <p>
841 * For example, the following will parse:
842 * <pre>
843 * "Europe/London" -> ZoneId.of("Europe/London")
844 * "Z" -> ZoneOffset.UTC
845 * "UT" -> ZoneOffset.UTC
846 * "UTC" -> ZoneOffset.UTC
847 * "GMT" -> ZoneOffset.UTC
848 * "UT0" -> ZoneOffset.UTC
849 * "UTC0" -> ZoneOffset.UTC
850 * "GMT0" -> ZoneOffset.UTC
851 * "+01:30" -> ZoneOffset.of("+01:30")
852 * "UT+01:30" -> ZoneOffset.of("+01:30")
853 * "UTC+01:30" -> ZoneOffset.of("+01:30")
854 * "GMT+01:30" -> ZoneOffset.of("+01:30")
855 * </pre>
856 * <p>
857 * Note that this method is is identical to {@code appendZoneId()} except
858 * in the mechanism used to obtain the zone.
859 *
860 * @return this, for chaining, not null
861 * @see #appendZoneId()
862 */
863 public DateTimeFormatterBuilder appendZoneOrOffsetId() {
864 appendInternal(new ZoneIdPrinterParser(Queries.zone(), "ZoneOrOffsetId()"));
865 return this;
866 }
867
868 /**
869 * Appends the time-zone name, such as 'British Summer Time', to the formatter.
870 * <p>
871 * This appends an instruction to format/parse the textual name of the zone to
872 * the builder.
873 * <p>
874 * During formatting, the zone is obtained using a mechanism equivalent
875 * to querying the temporal with {@link Queries#zoneId()}.
876 * If the zone is a {@code ZoneOffset} it will be printed using the
877 * result of {@link ZoneOffset#getId()}.
878 * If the zone is not an offset, the textual name will be looked up
879 * for the locale set in the {@link DateTimeFormatter}.
880 * If the temporal object being printed represents an instant, then the text
881 * will be the summer or winter time text as appropriate.
882 * If the lookup for text does not find any suitable reuslt, then the
883 * {@link ZoneId#getId() ID} will be printed instead.
884 * If the zone cannot be obtained then an exception is thrown unless the
885 * section of the formatter is optional.
886 * <p>
887 * During parsing, either the textual zone name, the zone ID or the offset
888 * is accepted. Many textual zone names are not unique, such as CST can be
889 * for both "Central Standard Time" and "China Standard Time". In this
890 * situation, the zone id will be determined by the region information from
891 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard
892 * zone id for that area, for example, America/New_York for the America Eastern
893 * zone. The {@link #appendZoneText(TextStyle, Set)} may be used
894 * to specify a set of preferred {@link ZoneId} in this situation.
895 *
896 * @param textStyle the text style to use, not null
897 * @return this, for chaining, not null
898 */
899 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
900 appendInternal(new ZoneTextPrinterParser(textStyle, null));
901 return this;
902 }
903
904 /**
905 * Appends the time-zone name, such as 'British Summer Time', to the formatter.
906 * <p>
907 * This appends an instruction to format/parse the textual name of the zone to
908 * the builder.
909 * <p>
910 * During formatting, the zone is obtained using a mechanism equivalent
911 * to querying the temporal with {@link Queries#zoneId()}.
912 * If the zone is a {@code ZoneOffset} it will be printed using the
913 * result of {@link ZoneOffset#getId()}.
914 * If the zone is not an offset, the textual name will be looked up
915 * for the locale set in the {@link DateTimeFormatter}.
916 * If the temporal object being printed represents an instant, then the text
917 * will be the summer or winter time text as appropriate.
918 * If the lookup for text does not find any suitable reuslt, then the
919 * {@link ZoneId#getId() ID} will be printed instead.
920 * If the zone cannot be obtained then an exception is thrown unless the
921 * section of the formatter is optional.
922 * <p>
923 * During parsing, either the textual zone name, the zone ID or the offset
924 * is accepted. Many textual zone names are not unique, such as CST can be
925 * for both "Central Standard Time" and "China Standard Time". In this
926 * situation, the zone id will be determined by the region information from
927 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard
928 * zone id for that area, for example, America/New_York for the America Eastern
929 * zone. This method also allows a set of preferred {@link ZoneId} to be
930 * specified for parsing. The matched preferred zone id will be used if the
931 * textural zone name being parsed is not unique.
934 * section of the formatter is optional.
935 *
936 * @param textStyle the text style to use, not null
937 * @param preferredZones the set of preferred zone ids, not null
938 * @return this, for chaining, not null
939 */
940 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle,
941 Set<ZoneId> preferredZones) {
942 Objects.requireNonNull(preferredZones, "preferredZones");
943 appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones));
944 return this;
945 }
946
947 //-----------------------------------------------------------------------
948 /**
949 * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
950 * <p>
951 * This appends an instruction to format/parse the chronology ID to the builder.
952 * <p>
953 * During formatting, the chronology is obtained using a mechanism equivalent
954 * to querying the temporal with {@link Queries#chronology()}.
955 * It will be printed using the result of {@link Chronology#getId()}.
956 * If the chronology cannot be obtained then an exception is thrown unless the
957 * section of the formatter is optional.
958 * <p>
959 * During parsing, the chronology is parsed and must match one of the chronologies
960 * in {@link Chronology#getAvailableChronologies()}.
961 * If the chronology cannot be parsed then an exception is thrown unless the
962 * section of the formatter is optional.
963 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
964 *
965 * @return this, for chaining, not null
966 */
967 public DateTimeFormatterBuilder appendChronologyId() {
968 appendInternal(new ChronoPrinterParser(null));
969 return this;
970 }
971
972 /**
973 * Appends the chronology name to the formatter.
974 * <p>
1081 * parts directly to this builder surrounded by an {@link #optionalStart()} and
1082 * {@link #optionalEnd()}.
1083 * <p>
1084 * The formatter will format if data is available for all the fields contained within it.
1085 * The formatter will parse if the string matches, otherwise no error is returned.
1086 *
1087 * @param formatter the formatter to add, not null
1088 * @return this, for chaining, not null
1089 */
1090 public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) {
1091 Objects.requireNonNull(formatter, "formatter");
1092 appendInternal(formatter.toPrinterParser(true));
1093 return this;
1094 }
1095
1096 //-----------------------------------------------------------------------
1097 /**
1098 * Appends the elements defined by the specified pattern to the builder.
1099 * <p>
1100 * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
1101 * The characters '{' and '}' are reserved for future use.
1102 * The characters '[' and ']' indicate optional patterns.
1103 * The following pattern letters are defined:
1104 * <pre>
1105 * Symbol Meaning Presentation Examples
1106 * ------ ------- ------------ -------
1107 * G era text A; AD; Anno Domini
1108 * y year year 2004; 04
1109 * D day-of-year number 189
1110 * M month-of-year number/text 7; 07; Jul; July; J
1111 * d day-of-month number 10
1112 *
1113 * Q quarter-of-year number/text 3; 03; Q3
1114 * Y week-based-year year 1996; 96
1115 * w week-of-year number 27
1116 * W week-of-month number 27
1117 * e localized day-of-week number 2; Tue; Tuesday; T
1118 * E day-of-week number/text 2; Tue; Tuesday; T
1119 * F week-of-month number 3
1120 *
1121 * a am-pm-of-day text PM
1122 * h clock-hour-of-am-pm (1-12) number 12
1123 * K hour-of-am-pm (0-11) number 0
1124 * k clock-hour-of-am-pm (1-24) number 0
1125 *
1126 * H hour-of-day (0-23) number 0
1127 * m minute-of-hour number 30
1128 * s second-of-minute number 55
1129 * S fraction-of-second fraction 978
1130 * A milli-of-day number 1234
1131 * n nano-of-second number 987654321
1132 * N nano-of-day number 1234000000
1133 *
1134 * V time-zone ID zone-id America/Los_Angeles; Z; -08:30
1135 * z time-zone name zone-name Pacific Standard Time; PST
1136 * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15;
1137 * x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15;
1138 * Z zone-offset offset-Z +0000; -0800; -08:00;
1139 *
1140 * p pad next pad modifier 1
1141 *
1142 * ' escape for text delimiter
1143 * '' single quote literal '
1144 * [ optional section start
1145 * ] optional section end
1146 * {} reserved for future use
1147 * </pre>
1148 * <p>
1149 * The count of pattern letters determine the format.
1150 * <p>
1151 * <b>Text</b>: The text style is determined based on the number of pattern letters used.
1152 * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}.
1153 * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}.
1154 * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}.
1155 * <p>
1156 * <b>Number</b>: If the count of letters is one, then the value is printed using the minimum number
1157 * of digits and without padding as per {@link #appendValue(java.time.temporal.TemporalField)}. Otherwise, the
1158 * count of digits is used as the width of the output field as per {@link #appendValue(java.time.temporal.TemporalField, int)}.
1159 * <p>
1160 * <b>Number/Text</b>: If the count of pattern letters is 3 or greater, use the Text rules above.
1161 * Otherwise use the Number rules above.
1162 * <p>
1163 * <b>Fraction</b>: Outputs the nano-of-second field as a fraction-of-second.
1164 * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9.
1165 * If it is less than 9, then the nano-of-second value is truncated, with only the most
1166 * significant digits being output.
1167 * When parsing in strict mode, the number of parsed digits must match the count of pattern letters.
1168 * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern
1169 * letters, up to 9 digits.
1170 * <p>
1171 * <b>Year</b>: The count of letters determines the minimum field width below which padding is used.
1172 * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used.
1173 * For formatting, this outputs the rightmost two digits. For parsing, this will parse using the
1174 * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive.
1175 * If the count of letters is less than four (but not two), then the sign is only output for negative
1176 * years as per {@link SignStyle#NORMAL}.
1177 * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD}
1178 * <p>
1179 * <b>ZoneId</b>: This outputs the time-zone ID, such as 'Europe/Paris'.
1180 * If the count of letters is two, then the time-zone ID is output.
1181 * Any other count of letters throws {@code IllegalArgumentException}.
1182 * <pre>
1183 * Pattern Equivalent builder methods
1184 * VV appendZoneId()
1185 * </pre>
1186 * <p>
1187 * <b>Zone names</b>: This outputs the display name of the time-zone ID.
1188 * If the count of letters is one, two or three, then the short name is output.
1189 * If the count of letters is four, then the full name is output.
1190 * Five or more letters throws {@code IllegalArgumentException}.
1191 * <pre>
1192 * Pattern Equivalent builder methods
1193 * z appendZoneText(TextStyle.SHORT)
1194 * zz appendZoneText(TextStyle.SHORT)
1195 * zzz appendZoneText(TextStyle.SHORT)
1196 * zzzz appendZoneText(TextStyle.FULL)
1197 * </pre>
1198 * <p>
1199 * <b>Offset X and x</b>: This formats the offset based on the number of pattern letters.
1200 * One letter outputs just the hour', such as '+01', unless the minute is non-zero
1201 * in which case the minute is also output, such as '+0130'.
1202 * Two letters outputs the hour and minute, without a colon, such as '+0130'.
1203 * Three letters outputs the hour and minute, with a colon, such as '+01:30'.
1204 * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'.
1205 * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'.
1206 * Six or more letters throws {@code IllegalArgumentException}.
1207 * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero,
1208 * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'.
1209 * <pre>
1210 * Pattern Equivalent builder methods
1211 * X appendOffset("+HHmm","Z")
1212 * XX appendOffset("+HHMM","Z")
1213 * XXX appendOffset("+HH:MM","Z")
1214 * XXXX appendOffset("+HHMMss","Z")
1215 * XXXXX appendOffset("+HH:MM:ss","Z")
1216 * x appendOffset("+HHmm","+00")
1217 * xx appendOffset("+HHMM","+0000")
1218 * xxx appendOffset("+HH:MM","+00:00")
1219 * xxxx appendOffset("+HHMMss","+0000")
1220 * xxxxx appendOffset("+HH:MM:ss","+00:00")
1221 * </pre>
1222 * <p>
1223 * <b>Offset Z</b>: This formats the offset based on the number of pattern letters.
1224 * One, two or three letters outputs the hour and minute, without a colon, such as '+0130'.
1225 * Four or more letters throws {@code IllegalArgumentException}.
1226 * The output will be '+0000' when the offset is zero.
1227 * <pre>
1228 * Pattern Equivalent builder methods
1229 * Z appendOffset("+HHMM","+0000")
1230 * ZZ appendOffset("+HHMM","+0000")
1231 * ZZZ appendOffset("+HHMM","+0000")
1232 * </pre>
1233 * <p>
1234 * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()}
1235 * and {@link #optionalEnd()}.
1236 * <p>
1237 * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces.
1238 * The pad width is determined by the number of pattern letters.
1239 * This is the same as calling {@link #padNext(int)}.
1240 * <p>
1241 * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2.
1242 * <p>
1243 * Any unrecognized letter is an error.
1244 * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly.
1245 * Despite this, it is recommended to use single quotes around all characters that you want to
1246 * output directly to ensure that future changes do not break your application.
1247 * <p>
1248 * Note that the pattern string is similar, but not identical, to
1249 * {@link java.text.SimpleDateFormat SimpleDateFormat}.
1250 * The pattern string is also similar, but not identical, to that defined by the
1251 * Unicode Common Locale Data Repository (CLDR/LDML).
1252 * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric.
1253 * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'.
1254 * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently.
1255 * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added.
1256 * Number types will reject large numbers.
1257 *
1258 * @param pattern the pattern to add, not null
1259 * @return this, for chaining, not null
1260 * @throws IllegalArgumentException if the pattern is invalid
1261 */
1262 public DateTimeFormatterBuilder appendPattern(String pattern) {
1263 Objects.requireNonNull(pattern, "pattern");
1264 parsePattern(pattern);
1265 return this;
1266 }
1267
1268 private void parsePattern(String pattern) {
1269 for (int pos = 0; pos < pattern.length(); pos++) {
1270 char cur = pattern.charAt(pos);
1271 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
1272 int start = pos++;
1273 for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop
1274 int count = pos - start;
1275 // padding
1291 padNext(pad); // pad and continue parsing
1292 }
1293 // main rules
1294 TemporalField field = FIELD_MAP.get(cur);
1295 if (field != null) {
1296 parseField(cur, count, field);
1297 } else if (cur == 'z') {
1298 if (count > 4) {
1299 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1300 } else if (count == 4) {
1301 appendZoneText(TextStyle.FULL);
1302 } else {
1303 appendZoneText(TextStyle.SHORT);
1304 }
1305 } else if (cur == 'V') {
1306 if (count != 2) {
1307 throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
1308 }
1309 appendZoneId();
1310 } else if (cur == 'Z') {
1311 if (count > 3) {
1312 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1313 }
1314 appendOffset("+HHMM", "+0000");
1315 } else if (cur == 'X') {
1316 if (count > 5) {
1317 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1318 }
1319 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
1320 } else if (cur == 'x') {
1321 if (count > 5) {
1322 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1323 }
1324 String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
1325 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
1326 } else if (cur == 'w' || cur == 'e') {
1327 // Fields defined by Locale
1328 if (count > 1) {
1329 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1330 }
1331 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1332 } else if (cur == 'W') {
1333 // Fields defined by Locale
1334 if (count > 2) {
1335 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1336 }
1337 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1338 } else {
1339 throw new IllegalArgumentException("Unknown pattern letter: " + cur);
1340 }
1341 pos--;
1342
1343 } else if (cur == '\'') {
1344 // parse literals
1345 int start = pos++;
1346 for ( ; pos < pattern.length(); pos++) {
1347 if (pattern.charAt(pos) == '\'') {
1348 if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') {
1349 pos++;
1350 } else {
1351 break; // end of literal
1352 }
1353 }
1354 }
1355 if (pos >= pattern.length()) {
1356 throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern);
1357 }
1358 String str = pattern.substring(start + 1, pos);
1359 if (str.length() == 0) {
1360 appendLiteral('\'');
1361 } else {
1362 appendLiteral(str.replace("''", "'"));
1363 }
1364
1365 } else if (cur == '[') {
1366 optionalStart();
1367
1368 } else if (cur == ']') {
1369 if (active.parent == null) {
1370 throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
1371 }
1372 optionalEnd();
1373
1374 } else if (cur == '{' || cur == '}') {
1375 throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
1376 } else {
1377 appendLiteral(cur);
1378 }
1379 }
1380 }
1381
1382 private void parseField(char cur, int count, TemporalField field) {
1383 switch (cur) {
1384 case 'y':
1385 case 'Y':
1386 if (count == 2) {
1387 appendValueReduced(field, 2, 2000);
1388 } else if (count < 4) {
1389 appendValue(field, count, 19, SignStyle.NORMAL);
1390 } else {
1391 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
1392 }
1393 break;
1394 case 'M':
1395 case 'Q':
1396 case 'E':
1397 switch (count) {
1398 case 1:
1399 appendValue(field);
1400 break;
1401 case 2:
1402 appendValue(field, 2);
1403 break;
1404 case 3:
1405 appendText(field, TextStyle.SHORT);
1406 break;
1407 case 4:
1408 appendText(field, TextStyle.FULL);
1409 break;
1410 case 5:
1411 appendText(field, TextStyle.NARROW);
1412 break;
1413 default:
1414 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1415 }
1416 break;
1417 case 'G':
1418 case 'a':
1419 switch (count) {
1420 case 1:
1421 case 2:
1422 case 3:
1423 appendText(field, TextStyle.SHORT);
1424 break;
1425 case 4:
1426 appendText(field, TextStyle.FULL);
1427 break;
1428 case 5:
1429 appendText(field, TextStyle.NARROW);
1430 break;
1431 default:
1432 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1433 }
1434 break;
1435 case 'S':
1436 appendFraction(NANO_OF_SECOND, count, count, false);
1437 break;
1438 default:
1439 if (count == 1) {
1440 appendValue(field);
1441 } else {
1442 appendValue(field, count);
1443 }
1444 break;
1445 }
1446 }
1447
1448 /** Map of letters to fields. */
1449 private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
1450 static {
1451 FIELD_MAP.put('G', ChronoField.ERA); // Java, LDML (different to both for 1/2 chars)
1452 FIELD_MAP.put('y', ChronoField.YEAR); // LDML
1453 // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, LDML // TODO redefine from above
1454 // FIELD_MAP.put('u', ChronoField.YEAR); // LDML // TODO
1455 // FIELD_MAP.put('Y', IsoFields.WEEK_BASED_YEAR); // Java7, LDML (needs localized week number) // TODO
1456 FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310)
1457 FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, LDML
1458 // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, LDML (needs localized week number)
1459 // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, LDML (needs localized week number)
1460 FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, LDML
1461 FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, LDML
1462 FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, LDML
1463 FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, LDML (different to both for 1/2 chars)
1464 // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // LDML (needs localized week number)
1465 FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, LDML
1466 FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, LDML
1467 FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, LDML
1468 FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, LDML
1469 FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, LDML
1470 FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, LDML
1471 FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, LDML
1472 FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (Java uses milli-of-second number)
1473 FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML
1474 FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML)
1475 FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML)
1476 // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
1477 // 310 - Z - matches SimpleDateFormat and LDML
1478 // 310 - V - time-zone id, matches proposed LDML
1479 // 310 - p - prefix for padding
1480 // 310 - X - matches proposed LDML, almost matches JavaSDF for 1, exact match 2&3, extended 4&5
1481 // 310 - x - matches proposed LDML
1482 // Java - u - clashes with LDML, go with LDML (year-proleptic) here
1483 // LDML - U - cycle year name, not supported by 310 yet
1484 // LDML - l - deprecated
1485 // LDML - j - not relevant
1486 // LDML - g - modified-julian-day
1487 // LDML - v,V - extended time-zone names
1488 // LDML - q/c/L - standalone quarter/day-of-week/month
1489 }
1490
1491 //-----------------------------------------------------------------------
1492 /**
1493 * Causes the next added printer/parser to pad to a fixed width using a space.
1494 * <p>
1495 * This padding will pad to a fixed width using spaces.
1496 * <p>
1497 * During formatting, the decorated element will be output and then padded
1498 * to the specified width. An exception will be thrown during formatting if
1499 * the pad width is exceeded.
1500 * <p>
1501 * During parsing, the padding and decorated element are parsed.
1502 * If parsing is lenient, then the pad width is treated as a maximum.
1503 * If parsing is case insensitive, then the pad character is matched ignoring case.
1504 * The padding is parsed greedily. Thus, if the decorated element starts with
1505 * the pad character, it will not be parsed.
1506 *
1507 * @param padWidth the pad width, 1 or greater
1508 * @return this, for chaining, not null
1615 *
1616 * @param pp the printer-parser to add, not null
1617 * @return the index into the active parsers list
1618 */
1619 private int appendInternal(DateTimePrinterParser pp) {
1620 Objects.requireNonNull(pp, "pp");
1621 if (active.padNextWidth > 0) {
1622 if (pp != null) {
1623 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
1624 }
1625 active.padNextWidth = 0;
1626 active.padNextChar = 0;
1627 }
1628 active.printerParsers.add(pp);
1629 active.valueParserIndex = -1;
1630 return active.printerParsers.size() - 1;
1631 }
1632
1633 //-----------------------------------------------------------------------
1634 /**
1635 * Completes this builder by creating the DateTimeFormatter using the default locale.
1636 * <p>
1637 * This will create a formatter with the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
1638 * Numbers will be printed and parsed using the standard non-localized set of symbols.
1639 * <p>
1640 * Calling this method will end any open optional sections by repeatedly
1641 * calling {@link #optionalEnd()} before creating the formatter.
1642 * <p>
1643 * This builder can still be used after creating the formatter if desired,
1644 * although the state may have been changed by calls to {@code optionalEnd}.
1645 *
1646 * @return the created formatter, not null
1647 */
1648 public DateTimeFormatter toFormatter() {
1649 return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
1650 }
1651
1652 /**
1653 * Completes this builder by creating the DateTimeFormatter using the specified locale.
1654 * <p>
1655 * This will create a formatter with the specified locale.
1656 * Numbers will be printed and parsed using the standard non-localized set of symbols.
1657 * <p>
1658 * Calling this method will end any open optional sections by repeatedly
1659 * calling {@link #optionalEnd()} before creating the formatter.
1660 * <p>
1661 * This builder can still be used after creating the formatter if desired,
1662 * although the state may have been changed by calls to {@code optionalEnd}.
1663 *
1664 * @param locale the locale to use for formatting, not null
1665 * @return the created formatter, not null
1666 */
1667 public DateTimeFormatter toFormatter(Locale locale) {
1668 Objects.requireNonNull(locale, "locale");
1669 while (active.parent != null) {
1670 optionalEnd();
1671 }
1672 CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
1673 return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null);
1674 }
1675
1676 //-----------------------------------------------------------------------
1677 /**
1678 * Strategy for formatting/parsing date-time information.
1679 * <p>
1680 * The printer may format any part, or the whole, of the input date-time object.
1681 * Typically, a complete format is constructed from a number of smaller
1682 * units, each outputting a single field.
1683 * <p>
1684 * The parser may parse any piece of text from the input, storing the result
1685 * in the context. Typically, each individual parser will just parse one
1686 * field, such as the day-of-month, storing the value in the context.
1687 * Once the parse is complete, the caller will then resolve the parsed values
1688 * to create the desired object, such as a {@code LocalDate}.
1689 * <p>
1690 * The parse position will be updated during the parse. Parsing will start at
1691 * the specified index and the return value specifies the new parse position
1692 * for the next parser. If an error occurs, the returned index will be negative
1693 * and will have the error position encoded using the complement operator.
1925 case 3: context.setStrict(false); break;
1926 }
1927 return position;
1928 }
1929
1930 @Override
1931 public String toString() {
1932 // using ordinals to avoid javac synthetic inner class
1933 switch (ordinal()) {
1934 case 0: return "ParseCaseSensitive(true)";
1935 case 1: return "ParseCaseSensitive(false)";
1936 case 2: return "ParseStrict(true)";
1937 case 3: return "ParseStrict(false)";
1938 }
1939 throw new IllegalStateException("Unreachable");
1940 }
1941 }
1942
1943 //-----------------------------------------------------------------------
1944 /**
1945 * Prints or parses a character literal.
1946 */
1947 static final class CharLiteralPrinterParser implements DateTimePrinterParser {
1948 private final char literal;
1949
1950 CharLiteralPrinterParser(char literal) {
1951 this.literal = literal;
1952 }
1953
1954 @Override
1955 public boolean format(DateTimePrintContext context, StringBuilder buf) {
1956 buf.append(literal);
1957 return true;
1958 }
1959
1960 @Override
1961 public int parse(DateTimeParseContext context, CharSequence text, int position) {
1962 int length = text.length();
1963 if (position == length) {
1964 return ~position;
2087 * Returns a new instance with fixed width flag set.
2088 *
2089 * @return a new updated printer-parser, not null
2090 */
2091 NumberPrinterParser withFixedWidth() {
2092 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1);
2093 }
2094
2095 /**
2096 * Returns a new instance with an updated subsequent width.
2097 *
2098 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater
2099 * @return a new updated printer-parser, not null
2100 */
2101 NumberPrinterParser withSubsequentWidth(int subsequentWidth) {
2102 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
2103 }
2104
2105 @Override
2106 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2107 Chronology chrono = context.getTemporal().query(Queries.chronology());
2108 Long valueLong;
2109 if (chrono == JapaneseChronology.INSTANCE && field == ChronoField.YEAR) {
2110 valueLong = context.getValue(ChronoField.YEAR_OF_ERA);
2111 } else {
2112 valueLong = context.getValue(field);
2113 }
2114 if (valueLong == null) {
2115 return false;
2116 }
2117 long value = getValue(valueLong);
2118 DateTimeFormatSymbols symbols = context.getSymbols();
2119 String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
2120 if (str.length() > maxWidth) {
2121 throw new DateTimeException("Field " + field.getName() +
2122 " cannot be printed as the value " + value +
2123 " exceeds the maximum print width of " + maxWidth);
2124 }
2125 str = symbols.convertNumberToI18N(str);
2126
2127 if (value >= 0) {
2128 switch (signStyle) {
2129 case EXCEEDS_PAD:
2130 if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
2131 buf.append(symbols.getPositiveSign());
2132 }
2133 break;
2264 if (totalBig.bitLength() > 63) {
2265 // overflow, parse 1 less digit
2266 totalBig = totalBig.divide(BigInteger.TEN);
2267 pos--;
2268 }
2269 return setValue(context, totalBig.longValue(), position, pos);
2270 }
2271 return setValue(context, total, position, pos);
2272 }
2273
2274 /**
2275 * Stores the value.
2276 *
2277 * @param context the context to store into, not null
2278 * @param value the value
2279 * @param errorPos the position of the field being parsed
2280 * @param successPos the position after the field being parsed
2281 * @return the new position
2282 */
2283 int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
2284 TemporalField f = field;
2285 if (field == ChronoField.YEAR) {
2286 Chronology chrono = context.getEffectiveChronology();
2287 if (chrono == JapaneseChronology.INSTANCE) {
2288 f = ChronoField.YEAR_OF_ERA;
2289 }
2290 }
2291 return context.setParsedField(f, value, errorPos, successPos);
2292 }
2293
2294 @Override
2295 public String toString() {
2296 if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
2297 return "Value(" + field.getName() + ")";
2298 }
2299 if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
2300 return "Value(" + field.getName() + "," + minWidth + ")";
2301 }
2302 return "Value(" + field.getName() + "," + minWidth + "," + maxWidth + "," + signStyle + ")";
2303 }
2304 }
2305
2306 //-----------------------------------------------------------------------
2307 /**
2308 * Prints and parses a reduced numeric date-time field.
2309 */
2310 static final class ReducedPrinterParser extends NumberPrinterParser {
2311 private final int baseValue;
2553 * Constructor.
2554 *
2555 * @param field the field to output, not null
2556 * @param textStyle the text style, not null
2557 * @param provider the text provider, not null
2558 */
2559 TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
2560 // validated by caller
2561 this.field = field;
2562 this.textStyle = textStyle;
2563 this.provider = provider;
2564 }
2565
2566 @Override
2567 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2568 Long value = context.getValue(field);
2569 if (value == null) {
2570 return false;
2571 }
2572 String text;
2573 Chronology chrono = context.getTemporal().query(Queries.chronology());
2574 if (chrono == null || chrono == IsoChronology.INSTANCE) {
2575 text = provider.getText(field, value, textStyle, context.getLocale());
2576 } else {
2577 text = provider.getText(chrono, field, value, textStyle, context.getLocale());
2578 }
2579 if (text == null) {
2580 return numberPrinterParser().format(context, buf);
2581 }
2582 buf.append(text);
2583 return true;
2584 }
2585
2586 @Override
2587 public int parse(DateTimeParseContext context, CharSequence parseText, int position) {
2588 int length = parseText.length();
2589 if (position < 0 || position > length) {
2590 throw new IndexOutOfBoundsException();
2591 }
2592 TextStyle style = (context.isStrict() ? textStyle : null);
2593 Chronology chrono = context.getEffectiveChronology();
2870 return required;
2871 }
2872 int value = (ch1 - 48) * 10 + (ch2 - 48);
2873 if (value < 0 || value > 59) {
2874 return required;
2875 }
2876 array[arrayIndex] = value;
2877 array[0] = pos;
2878 return false;
2879 }
2880
2881 @Override
2882 public String toString() {
2883 String converted = noOffsetText.replace("'", "''");
2884 return "Offset(" + PATTERNS[type] + ",'" + converted + "')";
2885 }
2886 }
2887
2888 //-----------------------------------------------------------------------
2889 /**
2890 * Prints or parses a zone ID.
2891 */
2892 static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
2893
2894 /** The text style to output. */
2895 private final TextStyle textStyle;
2896
2897 /** The preferred zoneid map */
2898 private Set<String> preferredZones;
2899
2900 ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
2901 super(Queries.zone(), "ZoneText(" + textStyle + ")");
2902 this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
2903 if (preferredZones != null && preferredZones.size() != 0) {
2904 this.preferredZones = new HashSet<>();
2905 for (ZoneId id : preferredZones) {
2906 this.preferredZones.add(id.getId());
2907 }
2908 }
2909 }
2910
2911 private static final int STD = 0;
2912 private static final int DST = 1;
2913 private static final int GENERIC = 2;
2914 private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
2915 new ConcurrentHashMap<>();
2916
2917 private String getDisplayName(String id, int type, Locale locale) {
2918 if (textStyle == TextStyle.NARROW) {
2919 return null;
2920 }
2921 String[] names;
2922 SoftReference<Map<Locale, String[]>> ref = cache.get(id);
2923 Map<Locale, String[]> perLocale = null;
2924 if (ref == null || (perLocale = ref.get()) == null ||
2925 (names = perLocale.get(locale)) == null) {
2926 names = TimeZoneNameUtility.retrieveDisplayNames(id, locale);
2927 if (names == null) {
2928 return null;
2929 }
2930 names = Arrays.copyOfRange(names, 0, 7);
2931 names[5] =
2932 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG,locale);
2933 if (names[5] == null) {
2934 names[5] = names[0]; // use the id
2935 }
2936 names[6] =
2937 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale);
2938 if (names[6] == null) {
2939 names[6] = names[0];
2940 }
2941 if (perLocale == null) {
2942 perLocale = new ConcurrentHashMap<>();
2943 }
2944 perLocale.put(locale, names);
2945 cache.put(id, new SoftReference<>(perLocale));
2946 }
2947 switch (type) {
2948 case STD:
2949 return names[textStyle.ordinal() + 1];
2950 case DST:
2951 return names[textStyle.ordinal() + 3];
2952 }
2953 return names[textStyle.ordinal() + 5];
2954 }
2955
2956 @Override
2957 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2958 ZoneId zone = context.getValue(Queries.zoneId());
2959 if (zone == null) {
2960 return false;
2961 }
2962 String zname = zone.getId();
2963 if (!(zone instanceof ZoneOffset)) {
2964 TemporalAccessor dt = context.getTemporal();
2965 String name = getDisplayName(zname,
2966 dt.isSupported(ChronoField.INSTANT_SECONDS)
2967 ? (zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD)
2968 : GENERIC,
2969 context.getLocale());
2970 if (name != null) {
2971 zname = name;
2972 }
2973 }
2974 buf.append(zname);
2975 return true;
2976 }
2977
2978 // cache per instance for now
3490 return value;
3491 }
3492 }
3493 }
3494
3495 //-----------------------------------------------------------------------
3496 /**
3497 * Prints or parses a chronology.
3498 */
3499 static final class ChronoPrinterParser implements DateTimePrinterParser {
3500 /** The text style to output, null means the ID. */
3501 private final TextStyle textStyle;
3502
3503 ChronoPrinterParser(TextStyle textStyle) {
3504 // validated by caller
3505 this.textStyle = textStyle;
3506 }
3507
3508 @Override
3509 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3510 Chronology chrono = context.getValue(Queries.chronology());
3511 if (chrono == null) {
3512 return false;
3513 }
3514 if (textStyle == null) {
3515 buf.append(chrono.getId());
3516 } else {
3517 buf.append(chrono.getId()); // TODO: Use symbols
3518 }
3519 return true;
3520 }
3521
3522 @Override
3523 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3524 // simple looping parser to find the chronology
3525 if (position < 0 || position > text.length()) {
3526 throw new IndexOutOfBoundsException();
3527 }
3528 Set<Chronology> chronos = Chronology.getAvailableChronologies();
3529 Chronology bestMatch = null;
3530 int matchLen = -1;
3531 for (Chronology chrono : chronos) {
3532 String id = chrono.getId();
3533 int idLen = id.length();
3534 if (idLen > matchLen && context.subSequenceEquals(text, position, id, 0, idLen)) {
3535 bestMatch = chrono;
3536 matchLen = idLen;
3537 }
3538 }
3539 if (bestMatch == null) {
3540 return ~position;
3541 }
3542 context.setParsed(bestMatch);
3543 return position + matchLen;
3544 }
3545 }
3546
3547 //-----------------------------------------------------------------------
3548 /**
3549 * Prints or parses a localized pattern.
3550 */
3551 static final class LocalizedPrinterParser implements DateTimePrinterParser {
3552 private final FormatStyle dateStyle;
3553 private final FormatStyle timeStyle;
3554
3555 /**
3556 * Constructor.
3557 *
3558 * @param dateStyle the date style to use, may be null
3559 * @param timeStyle the time style to use, may be null
3560 */
3561 LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) {
3562 // validated by caller
3563 this.dateStyle = dateStyle;
3564 this.timeStyle = timeStyle;
3565 }
3566
3567 @Override
3568 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3569 Chronology chrono = Chronology.from(context.getTemporal());
3570 return formatter(context.getLocale(), chrono).toPrinterParser(false).format(context, buf);
3571 }
3572
3573 @Override
3574 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3575 Chronology chrono = context.getEffectiveChronology();
3576 return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position);
3577 }
3578
3579 /**
3580 * Gets the formatter to use.
3581 *
3582 * @param locale the locale to use, not null
3583 * @param chrono the chronology to use, not null
3584 * @return the formatter, not null
3585 * @throws IllegalArgumentException if the formatter cannot be found
3586 */
3587 private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
3588 return DateTimeFormatStyleProvider.getInstance()
3589 .getFormatter(dateStyle, timeStyle, chrono, locale);
3590 }
3591
3592 @Override
3593 public String toString() {
3594 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
3595 (timeStyle != null ? timeStyle : "") + ")";
3596 }
3597 }
3598
3599
3600 //-----------------------------------------------------------------------
3601 /**
3602 * Prints or parses a localized pattern from a localized field.
3603 * The specific formatter and parameters is not selected until the
3604 * the field is to be printed or parsed.
3605 * The locale is needed to select the proper WeekFields from which
3606 * the field for day-of-week, week-of-month, or week-of-year is selected.
3607 */
3608 static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {
3609 private char chr;
3610 private int count;
3611
3612 /**
3613 * Constructor.
3614 *
3615 * @param chr the pattern format letter that added this PrinterParser.
3616 * @param count the repeat count of the format letter
3617 */
3618 WeekBasedFieldPrinterParser(char chr, int count) {
3619 this.chr = chr;
3624 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3625 return printerParser(context.getLocale()).format(context, buf);
3626 }
3627
3628 @Override
3629 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3630 return printerParser(context.getLocale()).parse(context, text, position);
3631 }
3632
3633 /**
3634 * Gets the printerParser to use based on the field and the locale.
3635 *
3636 * @param locale the locale to use, not null
3637 * @return the formatter, not null
3638 * @throws IllegalArgumentException if the formatter cannot be found
3639 */
3640 private DateTimePrinterParser printerParser(Locale locale) {
3641 WeekFields weekDef = WeekFields.of(locale);
3642 TemporalField field = null;
3643 switch (chr) {
3644 case 'e':
3645 field = weekDef.dayOfWeek();
3646 break;
3647 case 'w':
3648 field = weekDef.weekOfMonth();
3649 break;
3650 case 'W':
3651 field = weekDef.weekOfYear();
3652 break;
3653 default:
3654 throw new IllegalStateException("unreachable");
3655 }
3656 return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
3657 }
3658
3659 @Override
3660 public String toString() {
3661 return String.format("WeekBased(%c%d)", chr, count);
3662 }
3663 }
3664
3665
3666 //-------------------------------------------------------------------------
3667 /**
3668 * Length comparator.
3669 */
3670 static final Comparator<String> LENGTH_SORT = new Comparator<String>() {
3671 @Override
3672 public int compare(String str1, String str2) {
3673 return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
3674 }
3675 };
3676
3677 }
|
66 import static java.time.temporal.ChronoField.INSTANT_SECONDS;
67 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
68 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
69 import static java.time.temporal.ChronoField.NANO_OF_SECOND;
70 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
71 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
72 import static java.time.temporal.ChronoField.YEAR;
73
74 import java.lang.ref.SoftReference;
75 import java.math.BigDecimal;
76 import java.math.BigInteger;
77 import java.math.RoundingMode;
78 import java.text.ParsePosition;
79 import java.time.DateTimeException;
80 import java.time.Instant;
81 import java.time.LocalDateTime;
82 import java.time.ZoneId;
83 import java.time.ZoneOffset;
84 import java.time.chrono.Chronology;
85 import java.time.chrono.IsoChronology;
86 import java.time.format.DateTimeTextProvider.LocaleStore;
87 import java.time.temporal.ChronoField;
88 import java.time.temporal.IsoFields;
89 import java.time.temporal.TemporalAccessor;
90 import java.time.temporal.TemporalField;
91 import java.time.temporal.TemporalQuery;
92 import java.time.temporal.ValueRange;
93 import java.time.temporal.WeekFields;
94 import java.time.zone.ZoneRulesProvider;
95 import java.util.AbstractMap.SimpleImmutableEntry;
96 import java.util.ArrayList;
97 import java.util.Arrays;
98 import java.util.Collections;
99 import java.util.Comparator;
100 import java.util.HashMap;
101 import java.util.HashSet;
102 import java.util.Iterator;
103 import java.util.LinkedHashMap;
104 import java.util.List;
105 import java.util.Locale;
106 import java.util.Map;
107 import java.util.Map.Entry;
108 import java.util.Objects;
109 import java.util.Set;
110 import java.util.TimeZone;
111 import java.util.concurrent.ConcurrentHashMap;
112 import java.util.concurrent.ConcurrentMap;
113
114 import sun.util.locale.provider.LocaleProviderAdapter;
115 import sun.util.locale.provider.LocaleResources;
116 import sun.util.locale.provider.TimeZoneNameUtility;
117
118 /**
119 * Builder to create date-time formatters.
120 * <p>
121 * This allows a {@code DateTimeFormatter} to be created.
122 * All date-time formatters are created ultimately using this builder.
123 * <p>
124 * The basic elements of date-time can all be added:
125 * <p><ul>
126 * <li>Value - a numeric value</li>
127 * <li>Fraction - a fractional value including the decimal place. Always use this when
128 * outputting fractions to ensure that the fraction is parsed correctly</li>
129 * <li>Text - the textual equivalent for the value</li>
130 * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
131 * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
132 * <li>ZoneText - the name of the time-zone</li>
133 * <li>ChronologyId - the {@linkplain Chronology chronology} id</li>
134 * <li>ChronologyText - the name of the chronology</li>
135 * <li>Literal - a text literal</li>
136 * <li>Nested and Optional - formats can be nested or made optional</li>
137 * <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li>
138 * </ul><p>
139 * In addition, any of the elements may be decorated by padding, either with spaces or any other character.
140 * <p>
141 * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
142 * can be used, see {@link #appendPattern(String)}.
143 * In practice, this simply parses the pattern and calls other methods on the builder.
144 *
145 * <h3>Specification for implementors</h3>
146 * This class is a mutable builder intended for use from a single thread.
147 *
148 * @since 1.8
149 */
150 public final class DateTimeFormatterBuilder {
151
152 /**
153 * Query for a time-zone that is region-only.
154 */
155 private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
156 ZoneId zone = temporal.query(TemporalQuery.zoneId());
157 return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
158 };
159
160 /**
161 * The currently active builder, used by the outermost builder.
162 */
163 private DateTimeFormatterBuilder active = this;
164 /**
165 * The parent builder, null for the outermost builder.
166 */
167 private final DateTimeFormatterBuilder parent;
168 /**
169 * The list of printers that will be used.
170 */
171 private final List<DateTimePrinterParser> printerParsers = new ArrayList<>();
172 /**
173 * Whether this builder produces an optional formatter.
174 */
175 private final boolean optional;
176 /**
274 * Changes the parse style to be lenient for the remainder of the formatter.
275 * Note that case sensitivity is set separately to this method.
276 * <p>
277 * Parsing can be strict or lenient - by default its strict.
278 * This controls the degree of flexibility in matching the text and sign styles.
279 * Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
280 * <p>
281 * When used, this method changes the parsing to be lenient from this point onwards.
282 * The change will remain in force until the end of the formatter that is eventually
283 * constructed or until {@code parseStrict} is called.
284 *
285 * @return this, for chaining, not null
286 */
287 public DateTimeFormatterBuilder parseLenient() {
288 appendInternal(SettingsParser.LENIENT);
289 return this;
290 }
291
292 //-----------------------------------------------------------------------
293 /**
294 * Appends a default value for a field to the formatter for use in parsing.
295 * <p>
296 * This appends an instruction to the builder to inject a default value
297 * into the parsed result. This is especially useful in conjunction with
298 * optional parts of the formatter.
299 * <p>
300 * For example, consider a formatter that parses the year, followed by
301 * an optional month, with a further optional day-of-month. Using such a
302 * formatter would require the calling code to check whether a full date,
303 * year-month or just a year had been parsed. This method can be used to
304 * default the month and day-of-month to a sensible value, such as the
305 * first of the month, allowing the calling code to always get a date.
306 * <p>
307 * During formatting, this method has no effect.
308 * <p>
309 * During parsing, the current state of the parse is inspected.
310 * If the specified field has no associated value, because it has not been
311 * parsed successfully at that point, then the specified value is injected
312 * into the parse result. Injection is immediate, thus the field-value pair
313 * will be visible to any subsequent elements in the formatter.
314 * As such, this method is normally called at the end of the builder.
315 *
316 * @param field the field to default the value of, not null
317 * @param value the value to default the field to
318 * @return this, for chaining, not null
319 */
320 public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
321 Objects.requireNonNull(field, "field");
322 appendInternal(new DefaultValueParser(field, value));
323 return this;
324 }
325
326 //-----------------------------------------------------------------------
327 /**
328 * Appends the value of a date-time field to the formatter using a normal
329 * output style.
330 * <p>
331 * The value of the field will be output during a format.
332 * If the value cannot be obtained then an exception will be thrown.
333 * <p>
334 * The value will be printed as per the normal format of an integer value.
335 * Only negative numbers will be signed. No padding will be added.
336 * <p>
337 * The parser for a variable width value such as this normally behaves greedily,
338 * requiring one digit, but accepting as many digits as possible.
339 * This behavior can be affected by 'adjacent value parsing'.
340 * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
341 *
342 * @param field the field to append, not null
343 * @return this, for chaining, not null
344 */
345 public DateTimeFormatterBuilder appendValue(TemporalField field) {
346 Objects.requireNonNull(field, "field");
347 active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
675
676 /**
677 * Appends the zone offset, such as '+01:00', to the formatter.
678 * <p>
679 * This appends an instruction to format/parse the offset ID to the builder.
680 * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}.
681 *
682 * @return this, for chaining, not null
683 */
684 public DateTimeFormatterBuilder appendOffsetId() {
685 appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
686 return this;
687 }
688
689 /**
690 * Appends the zone offset, such as '+01:00', to the formatter.
691 * <p>
692 * This appends an instruction to format/parse the offset ID to the builder.
693 * <p>
694 * During formatting, the offset is obtained using a mechanism equivalent
695 * to querying the temporal with {@link TemporalQuery#offset()}.
696 * It will be printed using the format defined below.
697 * If the offset cannot be obtained then an exception is thrown unless the
698 * section of the formatter is optional.
699 * <p>
700 * During parsing, the offset is parsed using the format defined below.
701 * If the offset cannot be parsed then an exception is thrown unless the
702 * section of the formatter is optional.
703 * <p>
704 * The format of the offset is controlled by a pattern which must be one
705 * of the following:
706 * <p><ul>
707 * <li>{@code +HH} - hour only, ignoring minute and second
708 * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
709 * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
710 * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
711 * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
712 * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
713 * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
714 * <li>{@code +HHMMSS} - hour, minute and second, no colon
715 * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
716 * </ul><p>
717 * The "no offset" text controls what text is printed when the total amount of
718 * the offset fields to be output is zero.
719 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
720 * Three formats are accepted for parsing UTC - the "no offset" text, and the
721 * plus and minus versions of zero defined by the pattern.
722 *
723 * @param pattern the pattern to use, not null
724 * @param noOffsetText the text to use when the offset is zero, not null
725 * @return this, for chaining, not null
726 */
727 public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
728 appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
729 return this;
730 }
731
732 /**
733 * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
734 * <p>
735 * This appends a localized zone offset to the builder, the format of the
736 * localized offset is controlled by the specified {@link FormatStyle style}
737 * to this method:
738 * <p><ul>
739 * <li>{@link TextStyle#FULL full} - formats with localized offset text, such
740 * as 'GMT, 2-digit hour and minute field, optional second field if non-zero,
741 * and colon.
742 * <li>{@link TextStyle#SHORT short} - formats with localized offset text,
743 * such as 'GMT, hour without leading zero, optional 2-digit minute and
744 * second if non-zero, and colon.
745 * </ul><p>
746 * <p>
747 * During formatting, the offset is obtained using a mechanism equivalent
748 * to querying the temporal with {@link TemporalQuery#offset()}.
749 * If the offset cannot be obtained then an exception is thrown unless the
750 * section of the formatter is optional.
751 * <p>
752 * During parsing, the offset is parsed using the format defined above.
753 * If the offset cannot be parsed then an exception is thrown unless the
754 * section of the formatter is optional.
755 * <p>
756 * @param style the format style to use, not null
757 * @return this, for chaining, not null
758 * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL
759 * full} nor {@link TextStyle#SHORT short}
760 */
761 public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) {
762 Objects.requireNonNull(style, "style");
763 if (style != TextStyle.FULL && style != TextStyle.SHORT) {
764 throw new IllegalArgumentException("Style must be either full or short");
765 }
766 appendInternal(new LocalizedOffsetIdPrinterParser(style));
767 return this;
768 }
769
770 //-----------------------------------------------------------------------
771 /**
772 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
773 * <p>
774 * This appends an instruction to format/parse the zone ID to the builder.
775 * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
776 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable
777 * for use with this method, see {@link #appendZoneOrOffsetId()}.
778 * <p>
779 * During formatting, the zone is obtained using a mechanism equivalent
780 * to querying the temporal with {@link TemporalQuery#zoneId()}.
781 * It will be printed using the result of {@link ZoneId#getId()}.
782 * If the zone cannot be obtained then an exception is thrown unless the
783 * section of the formatter is optional.
784 * <p>
785 * During parsing, the text must match a known zone or offset.
786 * There are two types of zone ID, offset-based, such as '+01:30' and
787 * region-based, such as 'Europe/London'. These are parsed differently.
788 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
789 * expects an offset-based zone and will not match region-based zones.
790 * The offset ID, such as '+02:30', may be at the start of the parse,
791 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
792 * equivalent to using {@link #appendOffset(String, String)} using the
793 * arguments 'HH:MM:ss' and the no offset string '0'.
794 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
795 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
796 * In all other cases, the list of known region-based zones is used to
797 * find the longest available match. If no match is found, and the parse
798 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
799 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
800 * <p>
801 * For example, the following will parse:
802 * <pre>
803 * "Europe/London" -- ZoneId.of("Europe/London")
804 * "Z" -- ZoneOffset.UTC
805 * "UT" -- ZoneOffset.UTC
806 * "UTC" -- ZoneOffset.UTC
807 * "GMT" -- ZoneOffset.UTC
808 * "UT0" -- ZoneOffset.UTC
809 * "UTC0" -- ZoneOffset.UTC
810 * "GMT0" -- ZoneOffset.UTC
811 * "+01:30" -- ZoneOffset.of("+01:30")
812 * "UT+01:30" -- ZoneOffset.of("+01:30")
813 * "UTC+01:30" -- ZoneOffset.of("+01:30")
814 * "GMT+01:30" -- ZoneOffset.of("+01:30")
815 * </pre>
816 *
817 * @return this, for chaining, not null
818 * @see #appendZoneRegionId()
819 */
820 public DateTimeFormatterBuilder appendZoneId() {
821 appendInternal(new ZoneIdPrinterParser(TemporalQuery.zoneId(), "ZoneId()"));
822 return this;
823 }
824
825 /**
826 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
827 * rejecting the zone ID if it is a {@code ZoneOffset}.
828 * <p>
829 * This appends an instruction to format/parse the zone ID to the builder
830 * only if it is a region-based ID.
831 * <p>
832 * During formatting, the zone is obtained using a mechanism equivalent
833 * to querying the temporal with {@link TemporalQuery#zoneId()}.
834 * If the zone is a {@code ZoneOffset} or it cannot be obtained then
835 * an exception is thrown unless the section of the formatter is optional.
836 * If the zone is not an offset, then the zone will be printed using
837 * the zone ID from {@link ZoneId#getId()}.
838 * <p>
839 * During parsing, the text must match a known zone or offset.
840 * There are two types of zone ID, offset-based, such as '+01:30' and
841 * region-based, such as 'Europe/London'. These are parsed differently.
842 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
843 * expects an offset-based zone and will not match region-based zones.
844 * The offset ID, such as '+02:30', may be at the start of the parse,
845 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
846 * equivalent to using {@link #appendOffset(String, String)} using the
847 * arguments 'HH:MM:ss' and the no offset string '0'.
848 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
849 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
850 * In all other cases, the list of known region-based zones is used to
851 * find the longest available match. If no match is found, and the parse
852 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
853 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
854 * <p>
855 * For example, the following will parse:
856 * <pre>
857 * "Europe/London" -- ZoneId.of("Europe/London")
858 * "Z" -- ZoneOffset.UTC
859 * "UT" -- ZoneOffset.UTC
860 * "UTC" -- ZoneOffset.UTC
861 * "GMT" -- ZoneOffset.UTC
862 * "UT0" -- ZoneOffset.UTC
863 * "UTC0" -- ZoneOffset.UTC
864 * "GMT0" -- ZoneOffset.UTC
865 * "+01:30" -- ZoneOffset.of("+01:30")
866 * "UT+01:30" -- ZoneOffset.of("+01:30")
867 * "UTC+01:30" -- ZoneOffset.of("+01:30")
868 * "GMT+01:30" -- ZoneOffset.of("+01:30")
869 * </pre>
870 * <p>
871 * Note that this method is is identical to {@code appendZoneId()} except
872 * in the mechanism used to obtain the zone.
873 * Note also that parsing accepts offsets, whereas formatting will never
874 * produce one.
875 *
876 * @return this, for chaining, not null
877 * @see #appendZoneId()
878 */
879 public DateTimeFormatterBuilder appendZoneRegionId() {
880 appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()"));
881 return this;
882 }
883
884 /**
885 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to
886 * the formatter, using the best available zone ID.
887 * <p>
888 * This appends an instruction to format/parse the best available
889 * zone or offset ID to the builder.
890 * The zone ID is obtained in a lenient manner that first attempts to
891 * find a true zone ID, such as that on {@code ZonedDateTime}, and
892 * then attempts to find an offset, such as that on {@code OffsetDateTime}.
893 * <p>
894 * During formatting, the zone is obtained using a mechanism equivalent
895 * to querying the temporal with {@link TemporalQuery#zone()}.
896 * It will be printed using the result of {@link ZoneId#getId()}.
897 * If the zone cannot be obtained then an exception is thrown unless the
898 * section of the formatter is optional.
899 * <p>
900 * During parsing, the text must match a known zone or offset.
901 * There are two types of zone ID, offset-based, such as '+01:30' and
902 * region-based, such as 'Europe/London'. These are parsed differently.
903 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
904 * expects an offset-based zone and will not match region-based zones.
905 * The offset ID, such as '+02:30', may be at the start of the parse,
906 * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is
907 * equivalent to using {@link #appendOffset(String, String)} using the
908 * arguments 'HH:MM:ss' and the no offset string '0'.
909 * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
910 * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
911 * In all other cases, the list of known region-based zones is used to
912 * find the longest available match. If no match is found, and the parse
913 * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
914 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
915 * <p>
916 * For example, the following will parse:
917 * <pre>
918 * "Europe/London" -- ZoneId.of("Europe/London")
919 * "Z" -- ZoneOffset.UTC
920 * "UT" -- ZoneOffset.UTC
921 * "UTC" -- ZoneOffset.UTC
922 * "GMT" -- ZoneOffset.UTC
923 * "UT0" -- ZoneOffset.UTC
924 * "UTC0" -- ZoneOffset.UTC
925 * "GMT0" -- ZoneOffset.UTC
926 * "+01:30" -- ZoneOffset.of("+01:30")
927 * "UT+01:30" -- ZoneOffset.of("+01:30")
928 * "UTC+01:30" -- ZoneOffset.of("+01:30")
929 * "GMT+01:30" -- ZoneOffset.of("+01:30")
930 * </pre>
931 * <p>
932 * Note that this method is is identical to {@code appendZoneId()} except
933 * in the mechanism used to obtain the zone.
934 *
935 * @return this, for chaining, not null
936 * @see #appendZoneId()
937 */
938 public DateTimeFormatterBuilder appendZoneOrOffsetId() {
939 appendInternal(new ZoneIdPrinterParser(TemporalQuery.zone(), "ZoneOrOffsetId()"));
940 return this;
941 }
942
943 /**
944 * Appends the time-zone name, such as 'British Summer Time', to the formatter.
945 * <p>
946 * This appends an instruction to format/parse the textual name of the zone to
947 * the builder.
948 * <p>
949 * During formatting, the zone is obtained using a mechanism equivalent
950 * to querying the temporal with {@link TemporalQuery#zoneId()}.
951 * If the zone is a {@code ZoneOffset} it will be printed using the
952 * result of {@link ZoneOffset#getId()}.
953 * If the zone is not an offset, the textual name will be looked up
954 * for the locale set in the {@link DateTimeFormatter}.
955 * If the temporal object being printed represents an instant, then the text
956 * will be the summer or winter time text as appropriate.
957 * If the lookup for text does not find any suitable reuslt, then the
958 * {@link ZoneId#getId() ID} will be printed instead.
959 * If the zone cannot be obtained then an exception is thrown unless the
960 * section of the formatter is optional.
961 * <p>
962 * During parsing, either the textual zone name, the zone ID or the offset
963 * is accepted. Many textual zone names are not unique, such as CST can be
964 * for both "Central Standard Time" and "China Standard Time". In this
965 * situation, the zone id will be determined by the region information from
966 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard
967 * zone id for that area, for example, America/New_York for the America Eastern
968 * zone. The {@link #appendZoneText(TextStyle, Set)} may be used
969 * to specify a set of preferred {@link ZoneId} in this situation.
970 *
971 * @param textStyle the text style to use, not null
972 * @return this, for chaining, not null
973 */
974 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
975 appendInternal(new ZoneTextPrinterParser(textStyle, null));
976 return this;
977 }
978
979 /**
980 * Appends the time-zone name, such as 'British Summer Time', to the formatter.
981 * <p>
982 * This appends an instruction to format/parse the textual name of the zone to
983 * the builder.
984 * <p>
985 * During formatting, the zone is obtained using a mechanism equivalent
986 * to querying the temporal with {@link TemporalQuery#zoneId()}.
987 * If the zone is a {@code ZoneOffset} it will be printed using the
988 * result of {@link ZoneOffset#getId()}.
989 * If the zone is not an offset, the textual name will be looked up
990 * for the locale set in the {@link DateTimeFormatter}.
991 * If the temporal object being printed represents an instant, then the text
992 * will be the summer or winter time text as appropriate.
993 * If the lookup for text does not find any suitable reuslt, then the
994 * {@link ZoneId#getId() ID} will be printed instead.
995 * If the zone cannot be obtained then an exception is thrown unless the
996 * section of the formatter is optional.
997 * <p>
998 * During parsing, either the textual zone name, the zone ID or the offset
999 * is accepted. Many textual zone names are not unique, such as CST can be
1000 * for both "Central Standard Time" and "China Standard Time". In this
1001 * situation, the zone id will be determined by the region information from
1002 * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard
1003 * zone id for that area, for example, America/New_York for the America Eastern
1004 * zone. This method also allows a set of preferred {@link ZoneId} to be
1005 * specified for parsing. The matched preferred zone id will be used if the
1006 * textural zone name being parsed is not unique.
1009 * section of the formatter is optional.
1010 *
1011 * @param textStyle the text style to use, not null
1012 * @param preferredZones the set of preferred zone ids, not null
1013 * @return this, for chaining, not null
1014 */
1015 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle,
1016 Set<ZoneId> preferredZones) {
1017 Objects.requireNonNull(preferredZones, "preferredZones");
1018 appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones));
1019 return this;
1020 }
1021
1022 //-----------------------------------------------------------------------
1023 /**
1024 * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
1025 * <p>
1026 * This appends an instruction to format/parse the chronology ID to the builder.
1027 * <p>
1028 * During formatting, the chronology is obtained using a mechanism equivalent
1029 * to querying the temporal with {@link TemporalQuery#chronology()}.
1030 * It will be printed using the result of {@link Chronology#getId()}.
1031 * If the chronology cannot be obtained then an exception is thrown unless the
1032 * section of the formatter is optional.
1033 * <p>
1034 * During parsing, the chronology is parsed and must match one of the chronologies
1035 * in {@link Chronology#getAvailableChronologies()}.
1036 * If the chronology cannot be parsed then an exception is thrown unless the
1037 * section of the formatter is optional.
1038 * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
1039 *
1040 * @return this, for chaining, not null
1041 */
1042 public DateTimeFormatterBuilder appendChronologyId() {
1043 appendInternal(new ChronoPrinterParser(null));
1044 return this;
1045 }
1046
1047 /**
1048 * Appends the chronology name to the formatter.
1049 * <p>
1156 * parts directly to this builder surrounded by an {@link #optionalStart()} and
1157 * {@link #optionalEnd()}.
1158 * <p>
1159 * The formatter will format if data is available for all the fields contained within it.
1160 * The formatter will parse if the string matches, otherwise no error is returned.
1161 *
1162 * @param formatter the formatter to add, not null
1163 * @return this, for chaining, not null
1164 */
1165 public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) {
1166 Objects.requireNonNull(formatter, "formatter");
1167 appendInternal(formatter.toPrinterParser(true));
1168 return this;
1169 }
1170
1171 //-----------------------------------------------------------------------
1172 /**
1173 * Appends the elements defined by the specified pattern to the builder.
1174 * <p>
1175 * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
1176 * The characters '#', '{' and '}' are reserved for future use.
1177 * The characters '[' and ']' indicate optional patterns.
1178 * The following pattern letters are defined:
1179 * <pre>
1180 * Symbol Meaning Presentation Examples
1181 * ------ ------- ------------ -------
1182 * G era text AD; Anno Domini; A
1183 * u year year 2004; 04
1184 * y year-of-era year 2004; 04
1185 * D day-of-year number 189
1186 * M/L month-of-year number/text 7; 07; Jul; July; J
1187 * d day-of-month number 10
1188 *
1189 * Q/q quarter-of-year number/text 3; 03; Q3; 3rd quarter
1190 * Y week-based-year year 1996; 96
1191 * w week-of-week-based-year number 27
1192 * W week-of-month number 4
1193 * E day-of-week text Tue; Tuesday; T
1194 * e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T
1195 * F week-of-month number 3
1196 *
1197 * a am-pm-of-day text PM
1198 * h clock-hour-of-am-pm (1-12) number 12
1199 * K hour-of-am-pm (0-11) number 0
1200 * k clock-hour-of-am-pm (1-24) number 0
1201 *
1202 * H hour-of-day (0-23) number 0
1203 * m minute-of-hour number 30
1204 * s second-of-minute number 55
1205 * S fraction-of-second fraction 978
1206 * A milli-of-day number 1234
1207 * n nano-of-second number 987654321
1208 * N nano-of-day number 1234000000
1209 *
1210 * V time-zone ID zone-id America/Los_Angeles; Z; -08:30
1211 * z time-zone name zone-name Pacific Standard Time; PST
1212 * O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00;
1213 * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15;
1214 * x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15;
1215 * Z zone-offset offset-Z +0000; -0800; -08:00;
1216 *
1217 * p pad next pad modifier 1
1218 *
1219 * ' escape for text delimiter
1220 * '' single quote literal '
1221 * [ optional section start
1222 * ] optional section end
1223 * # reserved for future use
1224 * { reserved for future use
1225 * } reserved for future use
1226 * </pre>
1227 * <p>
1228 * The count of pattern letters determine the format.
1229 * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns.
1230 * The following tables define how the pattern letters map to the builder.
1231 * <p>
1232 * <b>Date fields</b>: Pattern letters to output a date.
1233 * <pre>
1234 * Pattern Count Equivalent builder methods
1235 * ------- ----- --------------------------
1236 * G 1 appendText(ChronoField.ERA, TextStyle.SHORT)
1237 * GG 2 appendText(ChronoField.ERA, TextStyle.SHORT)
1238 * GGG 3 appendText(ChronoField.ERA, TextStyle.SHORT)
1239 * GGGG 4 appendText(ChronoField.ERA, TextStyle.FULL)
1240 * GGGGG 5 appendText(ChronoField.ERA, TextStyle.NARROW)
1241 *
1242 * u 1 appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
1243 * uu 2 appendValueReduced(ChronoField.YEAR, 2, 2000);
1244 * uuu 3 appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
1245 * u..u 4..n appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
1246 * y 1 appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
1247 * yy 2 appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
1248 * yyy 3 appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
1249 * y..y 4..n appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
1250 * Y 1 append special localized WeekFields element for numeric week-based-year
1251 * YY 2 append special localized WeekFields element for reduced numeric week-based-year 2 digits;
1252 * YYY 3 append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
1253 * Y..Y 4..n append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);
1254 *
1255 * Q 1 appendValue(IsoFields.QUARTER_OF_YEAR);
1256 * QQ 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2);
1257 * QQQ 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
1258 * QQQQ 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
1259 * QQQQQ 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
1260 * q 1 appendValue(IsoFields.QUARTER_OF_YEAR);
1261 * qq 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2);
1262 * qqq 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
1263 * qqqq 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
1264 * qqqqq 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
1265 *
1266 * M 1 appendValue(ChronoField.MONTH_OF_YEAR);
1267 * MM 2 appendValue(ChronoField.MONTH_OF_YEAR, 2);
1268 * MMM 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
1269 * MMMM 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
1270 * MMMMM 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
1271 * L 1 appendValue(ChronoField.MONTH_OF_YEAR);
1272 * LL 2 appendValue(ChronoField.MONTH_OF_YEAR, 2);
1273 * LLL 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
1274 * LLLL 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
1275 * LLLLL 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
1276 *
1277 * w 1 append special localized WeekFields element for numeric week-of-year
1278 * ww 1 append special localized WeekFields element for numeric week-of-year, zero-padded
1279 * W 1 append special localized WeekFields element for numeric week-of-month
1280 * d 1 appendValue(ChronoField.DAY_OF_MONTH)
1281 * dd 2 appendValue(ChronoField.DAY_OF_MONTH, 2)
1282 * D 1 appendValue(ChronoField.DAY_OF_YEAR)
1283 * DD 2 appendValue(ChronoField.DAY_OF_YEAR, 2)
1284 * DDD 3 appendValue(ChronoField.DAY_OF_YEAR, 3)
1285 * F 1 appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
1286 * E 1 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
1287 * EE 2 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
1288 * EEE 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
1289 * EEEE 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
1290 * EEEEE 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
1291 * e 1 append special localized WeekFields element for numeric day-of-week
1292 * ee 2 append special localized WeekFields element for numeric day-of-week, zero-padded
1293 * eee 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
1294 * eeee 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
1295 * eeeee 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
1296 * c 1 append special localized WeekFields element for numeric day-of-week
1297 * ccc 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
1298 * cccc 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
1299 * ccccc 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
1300 * </pre>
1301 * <p>
1302 * <b>Time fields</b>: Pattern letters to output a time.
1303 * <pre>
1304 * Pattern Count Equivalent builder methods
1305 * ------- ----- --------------------------
1306 * a 1 appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
1307 * h 1 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
1308 * hh 2 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
1309 * H 1 appendValue(ChronoField.HOUR_OF_DAY)
1310 * HH 2 appendValue(ChronoField.HOUR_OF_DAY, 2)
1311 * k 1 appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
1312 * kk 2 appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
1313 * K 1 appendValue(ChronoField.HOUR_OF_AMPM)
1314 * KK 2 appendValue(ChronoField.HOUR_OF_AMPM, 2)
1315 * m 1 appendValue(ChronoField.MINUTE_OF_HOUR)
1316 * mm 2 appendValue(ChronoField.MINUTE_OF_HOUR, 2)
1317 * s 1 appendValue(ChronoField.SECOND_OF_MINUTE)
1318 * ss 2 appendValue(ChronoField.SECOND_OF_MINUTE, 2)
1319 *
1320 * S..S 1..n appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
1321 * A 1 appendValue(ChronoField.MILLI_OF_DAY)
1322 * A..A 2..n appendValue(ChronoField.MILLI_OF_DAY, n)
1323 * n 1 appendValue(ChronoField.NANO_OF_SECOND)
1324 * n..n 2..n appendValue(ChronoField.NANO_OF_SECOND, n)
1325 * N 1 appendValue(ChronoField.NANO_OF_DAY)
1326 * N..N 2..n appendValue(ChronoField.NANO_OF_DAY, n)
1327 * </pre>
1328 * <p>
1329 * <b>Zone ID</b>: Pattern letters to output {@code ZoneId}.
1330 * <pre>
1331 * Pattern Count Equivalent builder methods
1332 * ------- ----- --------------------------
1333 * VV 2 appendZoneId()
1334 * z 1 appendZoneText(TextStyle.SHORT)
1335 * zz 2 appendZoneText(TextStyle.SHORT)
1336 * zzz 3 appendZoneText(TextStyle.SHORT)
1337 * zzzz 4 appendZoneText(TextStyle.FULL)
1338 * </pre>
1339 * <p>
1340 * <b>Zone offset</b>: Pattern letters to output {@code ZoneOffset}.
1341 * <pre>
1342 * Pattern Count Equivalent builder methods
1343 * ------- ----- --------------------------
1344 * O 1 appendLocalizedOffsetPrefixed(TextStyle.SHORT);
1345 * OOOO 4 appendLocalizedOffsetPrefixed(TextStyle.FULL);
1346 * X 1 appendOffset("+HHmm","Z")
1347 * XX 2 appendOffset("+HHMM","Z")
1348 * XXX 3 appendOffset("+HH:MM","Z")
1349 * XXXX 4 appendOffset("+HHMMss","Z")
1350 * XXXXX 5 appendOffset("+HH:MM:ss","Z")
1351 * x 1 appendOffset("+HHmm","+00")
1352 * xx 2 appendOffset("+HHMM","+0000")
1353 * xxx 3 appendOffset("+HH:MM","+00:00")
1354 * xxxx 4 appendOffset("+HHMMss","+0000")
1355 * xxxxx 5 appendOffset("+HH:MM:ss","+00:00")
1356 * Z 1 appendOffset("+HHMM","+0000")
1357 * ZZ 2 appendOffset("+HHMM","+0000")
1358 * ZZZ 3 appendOffset("+HHMM","+0000")
1359 * ZZZZ 4 appendLocalizedOffset(TextStyle.FULL);
1360 * ZZZZZ 5 appendOffset("+HH:MM:ss","Z")
1361 * </pre>
1362 * <p>
1363 * <b>Modifiers</b>: Pattern letters that modify the rest of the pattern:
1364 * <pre>
1365 * Pattern Count Equivalent builder methods
1366 * ------- ----- --------------------------
1367 * [ 1 optionalStart()
1368 * ] 1 optionalEnd()
1369 * p..p 1..n padNext(n)
1370 * </pre>
1371 * <p>
1372 * Any sequence of letters not specified above, unrecognized letter or
1373 * reserved character will throw an exception.
1374 * Future versions may add to the set of patterns.
1375 * It is recommended to use single quotes around all characters that you want
1376 * to output directly to ensure that future changes do not break your application.
1377 * <p>
1378 * Note that the pattern string is similar, but not identical, to
1379 * {@link java.text.SimpleDateFormat SimpleDateFormat}.
1380 * The pattern string is also similar, but not identical, to that defined by the
1381 * Unicode Common Locale Data Repository (CLDR/LDML).
1382 * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML.
1383 * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week.
1384 * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently.
1385 * Pattern letters 'n', 'A', 'N', and 'p' are added.
1386 * Number types will reject large numbers.
1387 *
1388 * @param pattern the pattern to add, not null
1389 * @return this, for chaining, not null
1390 * @throws IllegalArgumentException if the pattern is invalid
1391 */
1392 public DateTimeFormatterBuilder appendPattern(String pattern) {
1393 Objects.requireNonNull(pattern, "pattern");
1394 parsePattern(pattern);
1395 return this;
1396 }
1397
1398 private void parsePattern(String pattern) {
1399 for (int pos = 0; pos < pattern.length(); pos++) {
1400 char cur = pattern.charAt(pos);
1401 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
1402 int start = pos++;
1403 for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop
1404 int count = pos - start;
1405 // padding
1421 padNext(pad); // pad and continue parsing
1422 }
1423 // main rules
1424 TemporalField field = FIELD_MAP.get(cur);
1425 if (field != null) {
1426 parseField(cur, count, field);
1427 } else if (cur == 'z') {
1428 if (count > 4) {
1429 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1430 } else if (count == 4) {
1431 appendZoneText(TextStyle.FULL);
1432 } else {
1433 appendZoneText(TextStyle.SHORT);
1434 }
1435 } else if (cur == 'V') {
1436 if (count != 2) {
1437 throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
1438 }
1439 appendZoneId();
1440 } else if (cur == 'Z') {
1441 if (count < 4) {
1442 appendOffset("+HHMM", "+0000");
1443 } else if (count == 4) {
1444 appendLocalizedOffset(TextStyle.FULL);
1445 } else if (count == 5) {
1446 appendOffset("+HH:MM:ss","Z");
1447 } else {
1448 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1449 }
1450 } else if (cur == 'O') {
1451 if (count == 1) {
1452 appendLocalizedOffset(TextStyle.SHORT);
1453 } else if (count == 4) {
1454 appendLocalizedOffset(TextStyle.FULL);
1455 } else {
1456 throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur);
1457 }
1458 } else if (cur == 'X') {
1459 if (count > 5) {
1460 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1461 }
1462 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
1463 } else if (cur == 'x') {
1464 if (count > 5) {
1465 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1466 }
1467 String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
1468 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
1469 } else if (cur == 'W') {
1470 // Fields defined by Locale
1471 if (count > 1) {
1472 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1473 }
1474 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1475 } else if (cur == 'w') {
1476 // Fields defined by Locale
1477 if (count > 2) {
1478 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1479 }
1480 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1481 } else if (cur == 'Y') {
1482 // Fields defined by Locale
1483 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1484 } else {
1485 throw new IllegalArgumentException("Unknown pattern letter: " + cur);
1486 }
1487 pos--;
1488
1489 } else if (cur == '\'') {
1490 // parse literals
1491 int start = pos++;
1492 for ( ; pos < pattern.length(); pos++) {
1493 if (pattern.charAt(pos) == '\'') {
1494 if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') {
1495 pos++;
1496 } else {
1497 break; // end of literal
1498 }
1499 }
1500 }
1501 if (pos >= pattern.length()) {
1502 throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern);
1503 }
1504 String str = pattern.substring(start + 1, pos);
1505 if (str.length() == 0) {
1506 appendLiteral('\'');
1507 } else {
1508 appendLiteral(str.replace("''", "'"));
1509 }
1510
1511 } else if (cur == '[') {
1512 optionalStart();
1513
1514 } else if (cur == ']') {
1515 if (active.parent == null) {
1516 throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
1517 }
1518 optionalEnd();
1519
1520 } else if (cur == '{' || cur == '}' || cur == '#') {
1521 throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
1522 } else {
1523 appendLiteral(cur);
1524 }
1525 }
1526 }
1527
1528 @SuppressWarnings("fallthrough")
1529 private void parseField(char cur, int count, TemporalField field) {
1530 boolean standalone = false;
1531 switch (cur) {
1532 case 'u':
1533 case 'y':
1534 if (count == 2) {
1535 appendValueReduced(field, 2, 2000);
1536 } else if (count < 4) {
1537 appendValue(field, count, 19, SignStyle.NORMAL);
1538 } else {
1539 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
1540 }
1541 break;
1542 case 'c':
1543 if (count == 2) {
1544 throw new IllegalArgumentException("Invalid pattern \"cc\"");
1545 }
1546 /*fallthrough*/
1547 case 'L':
1548 case 'q':
1549 standalone = true;
1550 /*fallthrough*/
1551 case 'M':
1552 case 'Q':
1553 case 'E':
1554 case 'e':
1555 switch (count) {
1556 case 1:
1557 case 2:
1558 if (cur == 'c' || cur == 'e') {
1559 appendInternal(new WeekBasedFieldPrinterParser(cur, count));
1560 } else if (cur == 'E') {
1561 appendText(field, TextStyle.SHORT);
1562 } else {
1563 if (count == 1) {
1564 appendValue(field);
1565 } else {
1566 appendValue(field, 2);
1567 }
1568 }
1569 break;
1570 case 3:
1571 appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
1572 break;
1573 case 4:
1574 appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
1575 break;
1576 case 5:
1577 appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
1578 break;
1579 default:
1580 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1581 }
1582 break;
1583 case 'a':
1584 if (count == 1) {
1585 appendText(field, TextStyle.SHORT);
1586 } else {
1587 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1588 }
1589 break;
1590 case 'G':
1591 switch (count) {
1592 case 1:
1593 case 2:
1594 case 3:
1595 appendText(field, TextStyle.SHORT);
1596 break;
1597 case 4:
1598 appendText(field, TextStyle.FULL);
1599 break;
1600 case 5:
1601 appendText(field, TextStyle.NARROW);
1602 break;
1603 default:
1604 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1605 }
1606 break;
1607 case 'S':
1608 appendFraction(NANO_OF_SECOND, count, count, false);
1609 break;
1610 case 'F':
1611 if (count == 1) {
1612 appendValue(field);
1613 } else {
1614 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1615 }
1616 break;
1617 case 'd':
1618 case 'h':
1619 case 'H':
1620 case 'k':
1621 case 'K':
1622 case 'm':
1623 case 's':
1624 if (count == 1) {
1625 appendValue(field);
1626 } else if (count == 2) {
1627 appendValue(field, count);
1628 } else {
1629 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1630 }
1631 break;
1632 case 'D':
1633 if (count == 1) {
1634 appendValue(field);
1635 } else if (count <= 3) {
1636 appendValue(field, count);
1637 } else {
1638 throw new IllegalArgumentException("Too many pattern letters: " + cur);
1639 }
1640 break;
1641 default:
1642 if (count == 1) {
1643 appendValue(field);
1644 } else {
1645 appendValue(field, count);
1646 }
1647 break;
1648 }
1649 }
1650
1651 /** Map of letters to fields. */
1652 private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
1653 static {
1654 // SDF = SimpleDateFormat
1655 FIELD_MAP.put('G', ChronoField.ERA); // SDF, LDML (different to both for 1/2 chars)
1656 FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // SDF, LDML
1657 FIELD_MAP.put('u', ChronoField.YEAR); // LDML (different in SDF)
1658 FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310)
1659 FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR); // LDML (stand-alone)
1660 FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // SDF, LDML
1661 FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR); // SDF, LDML (stand-alone)
1662 FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // SDF, LDML
1663 FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // SDF, LDML
1664 FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); // SDF, LDML
1665 FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // SDF, LDML (different to both for 1/2 chars)
1666 FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK); // LDML (stand-alone)
1667 FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK); // LDML (needs localized week number)
1668 FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // SDF, LDML
1669 FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // SDF, LDML
1670 FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // SDF, LDML
1671 FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // SDF, LDML
1672 FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // SDF, LDML
1673 FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // SDF, LDML
1674 FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // SDF, LDML
1675 FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (SDF uses milli-of-second number)
1676 FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML
1677 FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML)
1678 FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML)
1679 // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
1680 // 310 - Z - matches SimpleDateFormat and LDML
1681 // 310 - V - time-zone id, matches LDML
1682 // 310 - p - prefix for padding
1683 // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
1684 // 310 - x - matches LDML
1685 // 310 - w, W, and Y are localized forms matching LDML
1686 // LDML - U - cycle year name, not supported by 310 yet
1687 // LDML - l - deprecated
1688 // LDML - j - not relevant
1689 // LDML - g - modified-julian-day
1690 // LDML - v,V - extended time-zone names
1691 }
1692
1693 //-----------------------------------------------------------------------
1694 /**
1695 * Causes the next added printer/parser to pad to a fixed width using a space.
1696 * <p>
1697 * This padding will pad to a fixed width using spaces.
1698 * <p>
1699 * During formatting, the decorated element will be output and then padded
1700 * to the specified width. An exception will be thrown during formatting if
1701 * the pad width is exceeded.
1702 * <p>
1703 * During parsing, the padding and decorated element are parsed.
1704 * If parsing is lenient, then the pad width is treated as a maximum.
1705 * If parsing is case insensitive, then the pad character is matched ignoring case.
1706 * The padding is parsed greedily. Thus, if the decorated element starts with
1707 * the pad character, it will not be parsed.
1708 *
1709 * @param padWidth the pad width, 1 or greater
1710 * @return this, for chaining, not null
1817 *
1818 * @param pp the printer-parser to add, not null
1819 * @return the index into the active parsers list
1820 */
1821 private int appendInternal(DateTimePrinterParser pp) {
1822 Objects.requireNonNull(pp, "pp");
1823 if (active.padNextWidth > 0) {
1824 if (pp != null) {
1825 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
1826 }
1827 active.padNextWidth = 0;
1828 active.padNextChar = 0;
1829 }
1830 active.printerParsers.add(pp);
1831 active.valueParserIndex = -1;
1832 return active.printerParsers.size() - 1;
1833 }
1834
1835 //-----------------------------------------------------------------------
1836 /**
1837 * Completes this builder by creating the {@code DateTimeFormatter}
1838 * using the default locale.
1839 * <p>
1840 * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}.
1841 * Numbers will be printed and parsed using the standard non-localized set of symbols.
1842 * The resolver style will be {@link ResolverStyle#SMART SMART}.
1843 * <p>
1844 * Calling this method will end any open optional sections by repeatedly
1845 * calling {@link #optionalEnd()} before creating the formatter.
1846 * <p>
1847 * This builder can still be used after creating the formatter if desired,
1848 * although the state may have been changed by calls to {@code optionalEnd}.
1849 *
1850 * @return the created formatter, not null
1851 */
1852 public DateTimeFormatter toFormatter() {
1853 return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
1854 }
1855
1856 /**
1857 * Completes this builder by creating the {@code DateTimeFormatter}
1858 * using the specified locale.
1859 * <p>
1860 * This will create a formatter with the specified locale.
1861 * Numbers will be printed and parsed using the standard non-localized set of symbols.
1862 * The resolver style will be {@link ResolverStyle#SMART SMART}.
1863 * <p>
1864 * Calling this method will end any open optional sections by repeatedly
1865 * calling {@link #optionalEnd()} before creating the formatter.
1866 * <p>
1867 * This builder can still be used after creating the formatter if desired,
1868 * although the state may have been changed by calls to {@code optionalEnd}.
1869 *
1870 * @param locale the locale to use for formatting, not null
1871 * @return the created formatter, not null
1872 */
1873 public DateTimeFormatter toFormatter(Locale locale) {
1874 return toFormatter(locale, ResolverStyle.SMART, null);
1875 }
1876
1877 /**
1878 * Completes this builder by creating the formatter.
1879 * This uses the default locale.
1880 *
1881 * @param resolverStyle the resolver style to use, not null
1882 * @return the created formatter, not null
1883 */
1884 DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) {
1885 return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono);
1886 }
1887
1888 /**
1889 * Completes this builder by creating the formatter.
1890 *
1891 * @param locale the locale to use for formatting, not null
1892 * @param chrono the chronology to use, may be null
1893 * @return the created formatter, not null
1894 */
1895 private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
1896 Objects.requireNonNull(locale, "locale");
1897 while (active.parent != null) {
1898 optionalEnd();
1899 }
1900 CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
1901 return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD,
1902 resolverStyle, null, chrono, null);
1903 }
1904
1905 //-----------------------------------------------------------------------
1906 /**
1907 * Strategy for formatting/parsing date-time information.
1908 * <p>
1909 * The printer may format any part, or the whole, of the input date-time object.
1910 * Typically, a complete format is constructed from a number of smaller
1911 * units, each outputting a single field.
1912 * <p>
1913 * The parser may parse any piece of text from the input, storing the result
1914 * in the context. Typically, each individual parser will just parse one
1915 * field, such as the day-of-month, storing the value in the context.
1916 * Once the parse is complete, the caller will then resolve the parsed values
1917 * to create the desired object, such as a {@code LocalDate}.
1918 * <p>
1919 * The parse position will be updated during the parse. Parsing will start at
1920 * the specified index and the return value specifies the new parse position
1921 * for the next parser. If an error occurs, the returned index will be negative
1922 * and will have the error position encoded using the complement operator.
2154 case 3: context.setStrict(false); break;
2155 }
2156 return position;
2157 }
2158
2159 @Override
2160 public String toString() {
2161 // using ordinals to avoid javac synthetic inner class
2162 switch (ordinal()) {
2163 case 0: return "ParseCaseSensitive(true)";
2164 case 1: return "ParseCaseSensitive(false)";
2165 case 2: return "ParseStrict(true)";
2166 case 3: return "ParseStrict(false)";
2167 }
2168 throw new IllegalStateException("Unreachable");
2169 }
2170 }
2171
2172 //-----------------------------------------------------------------------
2173 /**
2174 * Defaults a value into the parse if not currently present.
2175 */
2176 static class DefaultValueParser implements DateTimePrinterParser {
2177 private final TemporalField field;
2178 private final long value;
2179
2180 DefaultValueParser(TemporalField field, long value) {
2181 this.field = field;
2182 this.value = value;
2183 }
2184
2185 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2186 return true;
2187 }
2188
2189 public int parse(DateTimeParseContext context, CharSequence text, int position) {
2190 if (context.getParsed(field) == null) {
2191 context.setParsedField(field, value, position, position);
2192 }
2193 return position;
2194 }
2195 }
2196
2197 //-----------------------------------------------------------------------
2198 /**
2199 * Prints or parses a character literal.
2200 */
2201 static final class CharLiteralPrinterParser implements DateTimePrinterParser {
2202 private final char literal;
2203
2204 CharLiteralPrinterParser(char literal) {
2205 this.literal = literal;
2206 }
2207
2208 @Override
2209 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2210 buf.append(literal);
2211 return true;
2212 }
2213
2214 @Override
2215 public int parse(DateTimeParseContext context, CharSequence text, int position) {
2216 int length = text.length();
2217 if (position == length) {
2218 return ~position;
2341 * Returns a new instance with fixed width flag set.
2342 *
2343 * @return a new updated printer-parser, not null
2344 */
2345 NumberPrinterParser withFixedWidth() {
2346 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1);
2347 }
2348
2349 /**
2350 * Returns a new instance with an updated subsequent width.
2351 *
2352 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater
2353 * @return a new updated printer-parser, not null
2354 */
2355 NumberPrinterParser withSubsequentWidth(int subsequentWidth) {
2356 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
2357 }
2358
2359 @Override
2360 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2361 Long valueLong = context.getValue(field);
2362 if (valueLong == null) {
2363 return false;
2364 }
2365 long value = getValue(valueLong);
2366 DateTimeFormatSymbols symbols = context.getSymbols();
2367 String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
2368 if (str.length() > maxWidth) {
2369 throw new DateTimeException("Field " + field.getName() +
2370 " cannot be printed as the value " + value +
2371 " exceeds the maximum print width of " + maxWidth);
2372 }
2373 str = symbols.convertNumberToI18N(str);
2374
2375 if (value >= 0) {
2376 switch (signStyle) {
2377 case EXCEEDS_PAD:
2378 if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
2379 buf.append(symbols.getPositiveSign());
2380 }
2381 break;
2512 if (totalBig.bitLength() > 63) {
2513 // overflow, parse 1 less digit
2514 totalBig = totalBig.divide(BigInteger.TEN);
2515 pos--;
2516 }
2517 return setValue(context, totalBig.longValue(), position, pos);
2518 }
2519 return setValue(context, total, position, pos);
2520 }
2521
2522 /**
2523 * Stores the value.
2524 *
2525 * @param context the context to store into, not null
2526 * @param value the value
2527 * @param errorPos the position of the field being parsed
2528 * @param successPos the position after the field being parsed
2529 * @return the new position
2530 */
2531 int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
2532 return context.setParsedField(field, value, errorPos, successPos);
2533 }
2534
2535 @Override
2536 public String toString() {
2537 if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
2538 return "Value(" + field.getName() + ")";
2539 }
2540 if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
2541 return "Value(" + field.getName() + "," + minWidth + ")";
2542 }
2543 return "Value(" + field.getName() + "," + minWidth + "," + maxWidth + "," + signStyle + ")";
2544 }
2545 }
2546
2547 //-----------------------------------------------------------------------
2548 /**
2549 * Prints and parses a reduced numeric date-time field.
2550 */
2551 static final class ReducedPrinterParser extends NumberPrinterParser {
2552 private final int baseValue;
2794 * Constructor.
2795 *
2796 * @param field the field to output, not null
2797 * @param textStyle the text style, not null
2798 * @param provider the text provider, not null
2799 */
2800 TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
2801 // validated by caller
2802 this.field = field;
2803 this.textStyle = textStyle;
2804 this.provider = provider;
2805 }
2806
2807 @Override
2808 public boolean format(DateTimePrintContext context, StringBuilder buf) {
2809 Long value = context.getValue(field);
2810 if (value == null) {
2811 return false;
2812 }
2813 String text;
2814 Chronology chrono = context.getTemporal().query(TemporalQuery.chronology());
2815 if (chrono == null || chrono == IsoChronology.INSTANCE) {
2816 text = provider.getText(field, value, textStyle, context.getLocale());
2817 } else {
2818 text = provider.getText(chrono, field, value, textStyle, context.getLocale());
2819 }
2820 if (text == null) {
2821 return numberPrinterParser().format(context, buf);
2822 }
2823 buf.append(text);
2824 return true;
2825 }
2826
2827 @Override
2828 public int parse(DateTimeParseContext context, CharSequence parseText, int position) {
2829 int length = parseText.length();
2830 if (position < 0 || position > length) {
2831 throw new IndexOutOfBoundsException();
2832 }
2833 TextStyle style = (context.isStrict() ? textStyle : null);
2834 Chronology chrono = context.getEffectiveChronology();
3111 return required;
3112 }
3113 int value = (ch1 - 48) * 10 + (ch2 - 48);
3114 if (value < 0 || value > 59) {
3115 return required;
3116 }
3117 array[arrayIndex] = value;
3118 array[0] = pos;
3119 return false;
3120 }
3121
3122 @Override
3123 public String toString() {
3124 String converted = noOffsetText.replace("'", "''");
3125 return "Offset(" + PATTERNS[type] + ",'" + converted + "')";
3126 }
3127 }
3128
3129 //-----------------------------------------------------------------------
3130 /**
3131 * Prints or parses an offset ID.
3132 */
3133 static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {
3134 private final TextStyle style;
3135
3136 /**
3137 * Constructor.
3138 *
3139 * @param style the style, not null
3140 */
3141 LocalizedOffsetIdPrinterParser(TextStyle style) {
3142 this.style = style;
3143 }
3144
3145 private static StringBuilder appendHMS(StringBuilder buf, int t) {
3146 return buf.append((char)(t / 10 + '0'))
3147 .append((char)(t % 10 + '0'));
3148 }
3149
3150 @Override
3151 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3152 Long offsetSecs = context.getValue(OFFSET_SECONDS);
3153 if (offsetSecs == null) {
3154 return false;
3155 }
3156 String gmtText = "GMT"; // TODO: get localized version of 'GMT'
3157 if (gmtText != null) {
3158 buf.append(gmtText);
3159 }
3160 int totalSecs = Math.toIntExact(offsetSecs);
3161 if (totalSecs != 0) {
3162 int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped
3163 int absMinutes = Math.abs((totalSecs / 60) % 60);
3164 int absSeconds = Math.abs(totalSecs % 60);
3165 buf.append(totalSecs < 0 ? "-" : "+");
3166 if (style == TextStyle.FULL) {
3167 appendHMS(buf, absHours);
3168 buf.append(':');
3169 appendHMS(buf, absMinutes);
3170 if (absSeconds != 0) {
3171 buf.append(':');
3172 appendHMS(buf, absSeconds);
3173 }
3174 } else {
3175 if (absHours >= 10) {
3176 buf.append((char)(absHours / 10 + '0'));
3177 }
3178 buf.append((char)(absHours % 10 + '0'));
3179 if (absMinutes != 0 || absSeconds != 0) {
3180 buf.append(':');
3181 appendHMS(buf, absMinutes);
3182 if (absSeconds != 0) {
3183 buf.append(':');
3184 appendHMS(buf, absSeconds);
3185 }
3186 }
3187 }
3188 }
3189 return true;
3190 }
3191
3192 int getDigit(CharSequence text, int position) {
3193 char c = text.charAt(position);
3194 if (c < '0' || c > '9') {
3195 return -1;
3196 }
3197 return c - '0';
3198 }
3199
3200 @Override
3201 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3202 int pos = position;
3203 int end = pos + text.length();
3204 String gmtText = "GMT"; // TODO: get localized version of 'GMT'
3205 if (gmtText != null) {
3206 if (!context.subSequenceEquals(text, pos, gmtText, 0, gmtText.length())) {
3207 return ~position;
3208 }
3209 pos += gmtText.length();
3210 }
3211 // parse normal plus/minus offset
3212 int negative = 0;
3213 if (pos == end) {
3214 return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
3215 }
3216 char sign = text.charAt(pos); // IOOBE if invalid position
3217 if (sign == '+') {
3218 negative = 1;
3219 } else if (sign == '-') {
3220 negative = -1;
3221 } else {
3222 return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
3223 }
3224 pos++;
3225 int h = 0;
3226 int m = 0;
3227 int s = 0;
3228 if (style == TextStyle.FULL) {
3229 int h1 = getDigit(text, pos++);
3230 int h2 = getDigit(text, pos++);
3231 if (h1 < 0 || h2 < 0 || text.charAt(pos++) != ':') {
3232 return ~position;
3233 }
3234 h = h1 * 10 + h2;
3235 int m1 = getDigit(text, pos++);
3236 int m2 = getDigit(text, pos++);
3237 if (m1 < 0 || m2 < 0) {
3238 return ~position;
3239 }
3240 m = m1 * 10 + m2;
3241 if (pos + 2 < end && text.charAt(pos) == ':') {
3242 int s1 = getDigit(text, pos + 1);
3243 int s2 = getDigit(text, pos + 2);
3244 if (s1 >= 0 && s2 >= 0) {
3245 s = s1 * 10 + s2;
3246 pos += 3;
3247 }
3248 }
3249 } else {
3250 h = getDigit(text, pos++);
3251 if (h < 0) {
3252 return ~position;
3253 }
3254 if (pos < end) {
3255 int h2 = getDigit(text, pos);
3256 if (h2 >=0) {
3257 h = h * 10 + h2;
3258 pos++;
3259 }
3260 if (pos + 2 < end && text.charAt(pos) == ':') {
3261 if (pos + 2 < end && text.charAt(pos) == ':') {
3262 int m1 = getDigit(text, pos + 1);
3263 int m2 = getDigit(text, pos + 2);
3264 if (m1 >= 0 && m2 >= 0) {
3265 m = m1 * 10 + m2;
3266 pos += 3;
3267 if (pos + 2 < end && text.charAt(pos) == ':') {
3268 int s1 = getDigit(text, pos + 1);
3269 int s2 = getDigit(text, pos + 2);
3270 if (s1 >= 0 && s2 >= 0) {
3271 s = s1 * 10 + s2;
3272 pos += 3;
3273 }
3274 }
3275 }
3276 }
3277 }
3278 }
3279 }
3280 long offsetSecs = negative * (h * 3600L + m * 60L + s);
3281 return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, pos);
3282 }
3283
3284 @Override
3285 public String toString() {
3286 return "LocalizedOffset(" + style + ")";
3287 }
3288 }
3289
3290 //-----------------------------------------------------------------------
3291 /**
3292 * Prints or parses a zone ID.
3293 */
3294 static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
3295
3296 /** The text style to output. */
3297 private final TextStyle textStyle;
3298
3299 /** The preferred zoneid map */
3300 private Set<String> preferredZones;
3301
3302 ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
3303 super(TemporalQuery.zone(), "ZoneText(" + textStyle + ")");
3304 this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
3305 if (preferredZones != null && preferredZones.size() != 0) {
3306 this.preferredZones = new HashSet<>();
3307 for (ZoneId id : preferredZones) {
3308 this.preferredZones.add(id.getId());
3309 }
3310 }
3311 }
3312
3313 private static final int STD = 0;
3314 private static final int DST = 1;
3315 private static final int GENERIC = 2;
3316 private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
3317 new ConcurrentHashMap<>();
3318
3319 private String getDisplayName(String id, int type, Locale locale) {
3320 if (textStyle == TextStyle.NARROW) {
3321 return null;
3322 }
3323 String[] names;
3324 SoftReference<Map<Locale, String[]>> ref = cache.get(id);
3325 Map<Locale, String[]> perLocale = null;
3326 if (ref == null || (perLocale = ref.get()) == null ||
3327 (names = perLocale.get(locale)) == null) {
3328 names = TimeZoneNameUtility.retrieveDisplayNames(id, locale);
3329 if (names == null) {
3330 return null;
3331 }
3332 names = Arrays.copyOfRange(names, 0, 7);
3333 names[5] =
3334 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG, locale);
3335 if (names[5] == null) {
3336 names[5] = names[0]; // use the id
3337 }
3338 names[6] =
3339 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT, locale);
3340 if (names[6] == null) {
3341 names[6] = names[0];
3342 }
3343 if (perLocale == null) {
3344 perLocale = new ConcurrentHashMap<>();
3345 }
3346 perLocale.put(locale, names);
3347 cache.put(id, new SoftReference<>(perLocale));
3348 }
3349 switch (type) {
3350 case STD:
3351 return names[textStyle.zoneNameStyleIndex() + 1];
3352 case DST:
3353 return names[textStyle.zoneNameStyleIndex() + 3];
3354 }
3355 return names[textStyle.zoneNameStyleIndex() + 5];
3356 }
3357
3358 @Override
3359 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3360 ZoneId zone = context.getValue(TemporalQuery.zoneId());
3361 if (zone == null) {
3362 return false;
3363 }
3364 String zname = zone.getId();
3365 if (!(zone instanceof ZoneOffset)) {
3366 TemporalAccessor dt = context.getTemporal();
3367 String name = getDisplayName(zname,
3368 dt.isSupported(ChronoField.INSTANT_SECONDS)
3369 ? (zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD)
3370 : GENERIC,
3371 context.getLocale());
3372 if (name != null) {
3373 zname = name;
3374 }
3375 }
3376 buf.append(zname);
3377 return true;
3378 }
3379
3380 // cache per instance for now
3892 return value;
3893 }
3894 }
3895 }
3896
3897 //-----------------------------------------------------------------------
3898 /**
3899 * Prints or parses a chronology.
3900 */
3901 static final class ChronoPrinterParser implements DateTimePrinterParser {
3902 /** The text style to output, null means the ID. */
3903 private final TextStyle textStyle;
3904
3905 ChronoPrinterParser(TextStyle textStyle) {
3906 // validated by caller
3907 this.textStyle = textStyle;
3908 }
3909
3910 @Override
3911 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3912 Chronology chrono = context.getValue(TemporalQuery.chronology());
3913 if (chrono == null) {
3914 return false;
3915 }
3916 if (textStyle == null) {
3917 buf.append(chrono.getId());
3918 } else {
3919 buf.append(getChronologyName(chrono, context.getLocale()));
3920 }
3921 return true;
3922 }
3923
3924 @Override
3925 public int parse(DateTimeParseContext context, CharSequence text, int position) {
3926 // simple looping parser to find the chronology
3927 if (position < 0 || position > text.length()) {
3928 throw new IndexOutOfBoundsException();
3929 }
3930 Set<Chronology> chronos = Chronology.getAvailableChronologies();
3931 Chronology bestMatch = null;
3932 int matchLen = -1;
3933 for (Chronology chrono : chronos) {
3934 String name;
3935 if (textStyle == null) {
3936 name = chrono.getId();
3937 } else {
3938 name = getChronologyName(chrono, context.getLocale());
3939 }
3940 int nameLen = name.length();
3941 if (nameLen > matchLen && context.subSequenceEquals(text, position, name, 0, nameLen)) {
3942 bestMatch = chrono;
3943 matchLen = nameLen;
3944 }
3945 }
3946 if (bestMatch == null) {
3947 return ~position;
3948 }
3949 context.setParsed(bestMatch);
3950 return position + matchLen;
3951 }
3952
3953 /**
3954 * Returns the chronology name of the given chrono in the given locale
3955 * if available, or the chronology Id otherwise. The regular ResourceBundle
3956 * search path is used for looking up the chronology name.
3957 *
3958 * @param chrono the chronology, not null
3959 * @param locale the locale, not null
3960 * @return the chronology name of chrono in locale, or the id if no name is available
3961 * @throws NullPointerException if chrono or locale is null
3962 */
3963 private String getChronologyName(Chronology chrono, Locale locale) {
3964 String key = "calendarname." + chrono.getCalendarType();
3965 String name = DateTimeTextProvider.getLocalizedResource(key, locale);
3966 return name != null ? name : chrono.getId();
3967 }
3968 }
3969
3970 //-----------------------------------------------------------------------
3971 /**
3972 * Prints or parses a localized pattern.
3973 */
3974 static final class LocalizedPrinterParser implements DateTimePrinterParser {
3975 /** Cache of formatters. */
3976 private static final ConcurrentMap<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
3977
3978 private final FormatStyle dateStyle;
3979 private final FormatStyle timeStyle;
3980
3981 /**
3982 * Constructor.
3983 *
3984 * @param dateStyle the date style to use, may be null
3985 * @param timeStyle the time style to use, may be null
3986 */
3987 LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) {
3988 // validated by caller
3989 this.dateStyle = dateStyle;
3990 this.timeStyle = timeStyle;
3991 }
3992
3993 @Override
3994 public boolean format(DateTimePrintContext context, StringBuilder buf) {
3995 Chronology chrono = Chronology.from(context.getTemporal());
3996 return formatter(context.getLocale(), chrono).toPrinterParser(false).format(context, buf);
3997 }
3998
3999 @Override
4000 public int parse(DateTimeParseContext context, CharSequence text, int position) {
4001 Chronology chrono = context.getEffectiveChronology();
4002 return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position);
4003 }
4004
4005 /**
4006 * Gets the formatter to use.
4007 * <p>
4008 * The formatter will be the most appropriate to use for the date and time style in the locale.
4009 * For example, some locales will use the month name while others will use the number.
4010 *
4011 * @param locale the locale to use, not null
4012 * @param chrono the chronology to use, not null
4013 * @return the formatter, not null
4014 * @throws IllegalArgumentException if the formatter cannot be found
4015 */
4016 private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
4017 String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle;
4018 DateTimeFormatter formatter = FORMATTER_CACHE.get(key);
4019 if (formatter == null) {
4020 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
4021 String pattern = lr.getJavaTimeDateTimePattern(
4022 convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType());
4023 formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
4024 DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
4025 if (old != null) {
4026 formatter = old;
4027 }
4028 }
4029 return formatter;
4030 }
4031
4032 /**
4033 * Converts the given FormatStyle to the java.text.DateFormat style.
4034 *
4035 * @param style the FormatStyle style
4036 * @return the int style, or -1 if style is null, indicating unrequired
4037 */
4038 private int convertStyle(FormatStyle style) {
4039 if (style == null) {
4040 return -1;
4041 }
4042 return style.ordinal(); // indices happen to align
4043 }
4044
4045 @Override
4046 public String toString() {
4047 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
4048 (timeStyle != null ? timeStyle : "") + ")";
4049 }
4050 }
4051
4052 //-----------------------------------------------------------------------
4053 /**
4054 * Prints or parses a localized pattern from a localized field.
4055 * The specific formatter and parameters is not selected until the
4056 * the field is to be printed or parsed.
4057 * The locale is needed to select the proper WeekFields from which
4058 * the field for day-of-week, week-of-month, or week-of-year is selected.
4059 */
4060 static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {
4061 private char chr;
4062 private int count;
4063
4064 /**
4065 * Constructor.
4066 *
4067 * @param chr the pattern format letter that added this PrinterParser.
4068 * @param count the repeat count of the format letter
4069 */
4070 WeekBasedFieldPrinterParser(char chr, int count) {
4071 this.chr = chr;
4076 public boolean format(DateTimePrintContext context, StringBuilder buf) {
4077 return printerParser(context.getLocale()).format(context, buf);
4078 }
4079
4080 @Override
4081 public int parse(DateTimeParseContext context, CharSequence text, int position) {
4082 return printerParser(context.getLocale()).parse(context, text, position);
4083 }
4084
4085 /**
4086 * Gets the printerParser to use based on the field and the locale.
4087 *
4088 * @param locale the locale to use, not null
4089 * @return the formatter, not null
4090 * @throws IllegalArgumentException if the formatter cannot be found
4091 */
4092 private DateTimePrinterParser printerParser(Locale locale) {
4093 WeekFields weekDef = WeekFields.of(locale);
4094 TemporalField field = null;
4095 switch (chr) {
4096 case 'Y':
4097 field = weekDef.weekBasedYear();
4098 if (count == 2) {
4099 return new ReducedPrinterParser(field, 2, 2000);
4100 } else {
4101 return new NumberPrinterParser(field, count, 19,
4102 (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
4103 }
4104 case 'e':
4105 case 'c':
4106 field = weekDef.dayOfWeek();
4107 break;
4108 case 'w':
4109 field = weekDef.weekOfWeekBasedYear();
4110 break;
4111 case 'W':
4112 field = weekDef.weekOfMonth();
4113 break;
4114 default:
4115 throw new IllegalStateException("unreachable");
4116 }
4117 return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
4118 }
4119
4120 @Override
4121 public String toString() {
4122 StringBuilder sb = new StringBuilder(30);
4123 sb.append("Localized(");
4124 if (chr == 'Y') {
4125 if (count == 1) {
4126 sb.append("WeekBasedYear");
4127 } else if (count == 2) {
4128 sb.append("ReducedValue(WeekBasedYear,2,2000)");
4129 } else {
4130 sb.append("WeekBasedYear,").append(count).append(",")
4131 .append(19).append(",")
4132 .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD);
4133 }
4134 } else {
4135 switch (chr) {
4136 case 'c':
4137 case 'e':
4138 sb.append("DayOfWeek");
4139 break;
4140 case 'w':
4141 sb.append("WeekOfWeekBasedYear");
4142 break;
4143 case 'W':
4144 sb.append("WeekOfMonth");
4145 break;
4146 default:
4147 break;
4148 }
4149 sb.append(",");
4150 sb.append(count);
4151 }
4152 sb.append(")");
4153 return sb.toString();
4154 }
4155 }
4156
4157 //-------------------------------------------------------------------------
4158 /**
4159 * Length comparator.
4160 */
4161 static final Comparator<String> LENGTH_SORT = new Comparator<String>() {
4162 @Override
4163 public int compare(String str1, String str2) {
4164 return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
4165 }
4166 };
4167 }
|