663 * section of the formatter is optional. |
665 * section of the formatter is optional. |
664 * <p> |
666 * <p> |
665 * The format of the offset is controlled by a pattern which must be one |
667 * The format of the offset is controlled by a pattern which must be one |
666 * of the following: |
668 * of the following: |
667 * <p><ul> |
669 * <p><ul> |
668 * <li>{@code +HH} - hour only, ignoring any minute |
670 * <li>{@code +HH} - hour only, ignoring minute and second |
669 * <li>{@code +HHMM} - hour and minute, no colon |
671 * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon |
670 * <li>{@code +HH:MM} - hour and minute, with colon |
672 * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon |
671 * <li>{@code +HHMMss} - hour and minute, with second if non-zero and no colon |
673 * <li>{@code +HHMM} - hour and minute, ignoring second, no colon |
672 * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero and 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 |
673 * <li>{@code +HHMMSS} - hour, minute and second, no colon |
677 * <li>{@code +HHMMSS} - hour, minute and second, no colon |
674 * <li>{@code +HH:MM:SS} - hour, minute and second, with colon |
678 * <li>{@code +HH:MM:SS} - hour, minute and second, with colon |
675 * </ul><p> |
679 * </ul><p> |
676 * The "no offset" text controls what text is printed when the offset is zero. |
680 * The "no offset" text controls what text is printed when the total amount of |
|
681 * the offset fields to be output is zero. |
677 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. |
682 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. |
678 * Three formats are accepted for parsing UTC - the "no offset" text, and the |
683 * Three formats are accepted for parsing UTC - the "no offset" text, and the |
679 * plus and minus versions of zero defined by the pattern. |
684 * plus and minus versions of zero defined by the pattern. |
680 * |
685 * |
681 * @param pattern the pattern to use, not null |
686 * @param pattern the pattern to use, not null |
682 * @param noOffsetText the text to use when the offset is zero, not null |
687 * @param noOffsetText the text to use when the offset is zero, not null |
683 * @return this, for chaining, not null |
688 * @return this, for chaining, not null |
684 */ |
689 */ |
685 public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) { |
690 public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) { |
686 appendInternal(new OffsetIdPrinterParser(noOffsetText, pattern)); |
691 appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText)); |
687 return this; |
692 return this; |
688 } |
693 } |
689 |
694 |
690 //----------------------------------------------------------------------- |
695 //----------------------------------------------------------------------- |
691 /** |
696 /** |
692 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. |
697 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. |
693 * <p> |
698 * <p> |
694 * This appends an instruction to print/parse the zone ID to the builder. |
699 * This appends an instruction to format/parse the zone ID to the builder. |
695 * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}. |
700 * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}. |
696 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable |
701 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable |
697 * for use with this method, see {@link #appendZoneOrOffsetId()}. |
702 * for use with this method, see {@link #appendZoneOrOffsetId()}. |
698 * <p> |
703 * <p> |
699 * During printing, the zone is obtained using a mechanism equivalent |
704 * During formatting, the zone is obtained using a mechanism equivalent |
700 * to querying the temporal with {@link Queries#zoneId()}. |
705 * to querying the temporal with {@link Queries#zoneId()}. |
701 * It will be printed using the result of {@link ZoneId#getId()}. |
706 * It will be printed using the result of {@link ZoneId#getId()}. |
702 * If the zone cannot be obtained then an exception is thrown unless the |
707 * If the zone cannot be obtained then an exception is thrown unless the |
703 * section of the formatter is optional. |
708 * section of the formatter is optional. |
704 * <p> |
709 * <p> |
705 * During parsing, the zone is parsed and must match a known zone or offset. |
710 * During parsing, the text must match a known zone or offset. |
706 * If the zone cannot be parsed then an exception is thrown unless the |
711 * There are two types of zone ID, offset-based, such as '+01:30' and |
707 * section of the formatter is optional. |
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> |
708 * |
741 * |
709 * @return this, for chaining, not null |
742 * @return this, for chaining, not null |
710 * @see #appendZoneRegionId() |
743 * @see #appendZoneRegionId() |
711 */ |
744 */ |
712 public DateTimeFormatterBuilder appendZoneId() { |
745 public DateTimeFormatterBuilder appendZoneId() { |
716 |
749 |
717 /** |
750 /** |
718 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, |
751 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, |
719 * rejecting the zone ID if it is a {@code ZoneOffset}. |
752 * rejecting the zone ID if it is a {@code ZoneOffset}. |
720 * <p> |
753 * <p> |
721 * This appends an instruction to print/parse the zone ID to the builder |
754 * This appends an instruction to format/parse the zone ID to the builder |
722 * only if it is a region-based ID. |
755 * only if it is a region-based ID. |
723 * <p> |
756 * <p> |
724 * During printing, the zone is obtained using a mechanism equivalent |
757 * During formatting, the zone is obtained using a mechanism equivalent |
725 * to querying the temporal with {@link Queries#zoneId()}. |
758 * to querying the temporal with {@link Queries#zoneId()}. |
726 * If the zone is a {@code ZoneOffset} or it cannot be obtained then |
759 * If the zone is a {@code ZoneOffset} or it cannot be obtained then |
727 * an exception is thrown unless the section of the formatter is optional. |
760 * an exception is thrown unless the section of the formatter is optional. |
728 * If the zone is not an offset, then the zone will be printed using |
761 * If the zone is not an offset, then the zone will be printed using |
729 * the zone ID from {@link ZoneId#getId()}. |
762 * the zone ID from {@link ZoneId#getId()}. |
730 * <p> |
763 * <p> |
731 * During parsing, the zone is parsed and must match a known zone or offset. |
764 * During parsing, the text must match a known zone or offset. |
732 * If the zone cannot be parsed then an exception is thrown unless the |
765 * There are two types of zone ID, offset-based, such as '+01:30' and |
733 * section of the formatter is optional. |
766 * region-based, such as 'Europe/London'. These are parsed differently. |
734 * Note that parsing accepts offsets, whereas printing will never produce |
767 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser |
735 * one, thus parsing is equivalent to {@code appendZoneId}. |
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. |
736 * |
800 * |
737 * @return this, for chaining, not null |
801 * @return this, for chaining, not null |
738 * @see #appendZoneId() |
802 * @see #appendZoneId() |
739 */ |
803 */ |
740 public DateTimeFormatterBuilder appendZoneRegionId() { |
804 public DateTimeFormatterBuilder appendZoneRegionId() { |
744 |
808 |
745 /** |
809 /** |
746 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to |
810 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to |
747 * the formatter, using the best available zone ID. |
811 * the formatter, using the best available zone ID. |
748 * <p> |
812 * <p> |
749 * This appends an instruction to print/parse the best available |
813 * This appends an instruction to format/parse the best available |
750 * zone or offset ID to the builder. |
814 * zone or offset ID to the builder. |
751 * The zone ID is obtained in a lenient manner that first attempts to |
815 * The zone ID is obtained in a lenient manner that first attempts to |
752 * find a true zone ID, such as that on {@code ZonedDateTime}, and |
816 * find a true zone ID, such as that on {@code ZonedDateTime}, and |
753 * then attempts to find an offset, such as that on {@code OffsetDateTime}. |
817 * then attempts to find an offset, such as that on {@code OffsetDateTime}. |
754 * <p> |
818 * <p> |
755 * During printing, the zone is obtained using a mechanism equivalent |
819 * During formatting, the zone is obtained using a mechanism equivalent |
756 * to querying the temporal with {@link Queries#zone()}. |
820 * to querying the temporal with {@link Queries#zone()}. |
757 * It will be printed using the result of {@link ZoneId#getId()}. |
821 * It will be printed using the result of {@link ZoneId#getId()}. |
758 * If the zone cannot be obtained then an exception is thrown unless the |
822 * If the zone cannot be obtained then an exception is thrown unless the |
759 * section of the formatter is optional. |
823 * section of the formatter is optional. |
760 * <p> |
824 * <p> |
761 * During parsing, the zone is parsed and must match a known zone or offset. |
825 * During parsing, the text must match a known zone or offset. |
762 * If the zone cannot be parsed then an exception is thrown unless the |
826 * There are two types of zone ID, offset-based, such as '+01:30' and |
763 * section of the formatter is optional. |
827 * region-based, such as 'Europe/London'. These are parsed differently. |
764 * <p> |
828 * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser |
765 * This method is is identical to {@code appendZoneId()} except in the |
829 * expects an offset-based zone and will not match region-based zones. |
766 * mechanism used to obtain the zone. |
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. |
767 * |
859 * |
768 * @return this, for chaining, not null |
860 * @return this, for chaining, not null |
769 * @see #appendZoneId() |
861 * @see #appendZoneId() |
770 */ |
862 */ |
771 public DateTimeFormatterBuilder appendZoneOrOffsetId() { |
863 public DateTimeFormatterBuilder appendZoneOrOffsetId() { |
789 * If the lookup for text does not find any suitable reuslt, then the |
882 * If the lookup for text does not find any suitable reuslt, then the |
790 * {@link ZoneId#getId() ID} will be printed instead. |
883 * {@link ZoneId#getId() ID} will be printed instead. |
791 * If the zone cannot be obtained then an exception is thrown unless the |
884 * If the zone cannot be obtained then an exception is thrown unless the |
792 * section of the formatter is optional. |
885 * section of the formatter is optional. |
793 * <p> |
886 * <p> |
794 * Parsing is not currently supported. |
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. |
795 * |
895 * |
796 * @param textStyle the text style to use, not null |
896 * @param textStyle the text style to use, not null |
797 * @return this, for chaining, not null |
897 * @return this, for chaining, not null |
798 */ |
898 */ |
799 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) { |
899 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) { |
800 // TODO: parsing of zone text? |
900 appendInternal(new ZoneTextPrinterParser(textStyle, null)); |
801 // * During parsing, either the textual zone name, the zone ID or the offset |
|
802 // * is accepted. |
|
803 // * If the zone cannot be parsed then an exception is thrown unless the |
|
804 // * section of the formatter is optional. |
|
805 appendInternal(new ZoneTextPrinterParser(textStyle)); |
|
806 return this; |
901 return this; |
807 } |
902 } |
808 |
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. |
|
932 * |
|
933 * If the zone cannot be parsed then an exception is thrown unless the |
|
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 |
809 //----------------------------------------------------------------------- |
947 //----------------------------------------------------------------------- |
810 /** |
948 /** |
811 * Appends the chronology ID to the formatter. |
949 * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter. |
812 * <p> |
950 * <p> |
813 * The chronology ID will be output during a print. |
951 * This appends an instruction to format/parse the chronology ID to the builder. |
814 * If the chronology cannot be obtained then an exception will be thrown. |
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. |
815 * |
964 * |
816 * @return this, for chaining, not null |
965 * @return this, for chaining, not null |
817 */ |
966 */ |
818 public DateTimeFormatterBuilder appendChronoId() { |
967 public DateTimeFormatterBuilder appendChronologyId() { |
819 appendInternal(new ChronoPrinterParser(null)); |
968 appendInternal(new ChronoPrinterParser(null)); |
820 return this; |
969 return this; |
821 } |
970 } |
822 |
971 |
823 /** |
972 /** |
824 * Appends the chronology name to the formatter. |
973 * Appends the chronology name to the formatter. |
825 * <p> |
974 * <p> |
826 * The calendar system name will be output during a print. |
975 * The calendar system name will be output during a format. |
827 * If the chronology cannot be obtained then an exception will be thrown. |
976 * If the chronology cannot be obtained then an exception will be thrown. |
828 * The calendar system name is obtained from the formatting symbols. |
977 * The calendar system name is obtained from the formatting symbols. |
829 * |
978 * |
830 * @param textStyle the text style to use, not null |
979 * @param textStyle the text style to use, not null |
831 * @return this, for chaining, not null |
980 * @return this, for chaining, not null |
832 */ |
981 */ |
833 public DateTimeFormatterBuilder appendChronoText(TextStyle textStyle) { |
982 public DateTimeFormatterBuilder appendChronologyText(TextStyle textStyle) { |
834 Objects.requireNonNull(textStyle, "textStyle"); |
983 Objects.requireNonNull(textStyle, "textStyle"); |
835 appendInternal(new ChronoPrinterParser(textStyle)); |
984 appendInternal(new ChronoPrinterParser(textStyle)); |
836 return this; |
985 return this; |
837 } |
986 } |
838 |
987 |
839 //----------------------------------------------------------------------- |
988 //----------------------------------------------------------------------- |
840 /** |
989 /** |
841 * Appends a localized date-time pattern to the formatter. |
990 * Appends a localized date-time pattern to the formatter. |
842 * <p> |
991 * <p> |
843 * The pattern is resolved lazily using the locale being used during the print/parse |
992 * This appends a localized section to the builder, suitable for outputting |
844 * (stored in {@link DateTimeFormatter}. |
993 * a date, time or date-time combination. The format of the localized |
845 * <p> |
994 * section is lazily looked up based on four items: |
846 * The pattern can vary by chronology, although typically it doesn't. |
995 * <p><ul> |
847 * This method uses the standard ISO chronology patterns. |
996 * <li>the {@code dateStyle} specified to this method |
|
997 * <li>the {@code timeStyle} specified to this method |
|
998 * <li>the {@code Locale} of the {@code DateTimeFormatter} |
|
999 * <li>the {@code Chronology}, selecting the best available |
|
1000 * </ul><p> |
|
1001 * During formatting, the chronology is obtained from the temporal object |
|
1002 * being formatted, which may have been overridden by |
|
1003 * {@link DateTimeFormatter#withChronology(Chronology)}. |
|
1004 * <p> |
|
1005 * During parsing, if a chronology has already been parsed, then it is used. |
|
1006 * Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)} |
|
1007 * is used, with {@code IsoChronology} as the fallback. |
|
1008 * <p> |
|
1009 * Note that this method provides similar functionality to methods on |
|
1010 * {@code DateFormat} such as {@link DateFormat#getDateTimeInstance(int, int)}. |
848 * |
1011 * |
849 * @param dateStyle the date style to use, null means no date required |
1012 * @param dateStyle the date style to use, null means no date required |
850 * @param timeStyle the time style to use, null means no time required |
1013 * @param timeStyle the time style to use, null means no time required |
851 * @return this, for chaining, not null |
1014 * @return this, for chaining, not null |
|
1015 * @throws IllegalArgumentException if both the date and time styles are null |
852 */ |
1016 */ |
853 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) { |
1017 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) { |
854 return appendLocalized(dateStyle, timeStyle, ISOChrono.INSTANCE); |
1018 if (dateStyle == null && timeStyle == null) { |
855 } |
1019 throw new IllegalArgumentException("Either the date or time style must be non-null"); |
856 |
1020 } |
857 /** |
1021 appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle)); |
858 * Appends a localized date-time pattern to the formatter. |
|
859 * <p> |
|
860 * The pattern is resolved lazily using the locale being used during the print/parse, |
|
861 * stored in {@link DateTimeFormatter}. |
|
862 * <p> |
|
863 * The pattern can vary by chronology, although typically it doesn't. |
|
864 * This method allows the chronology to be specified. |
|
865 * |
|
866 * @param dateStyle the date style to use, null means no date required |
|
867 * @param timeStyle the time style to use, null means no time required |
|
868 * @param chrono the chronology to use, not null |
|
869 * @return this, for chaining, not null |
|
870 */ |
|
871 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) { |
|
872 Objects.requireNonNull(chrono, "chrono"); |
|
873 if (dateStyle != null || timeStyle != null) { |
|
874 appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle, chrono)); |
|
875 } |
|
876 return this; |
1022 return this; |
877 } |
1023 } |
878 |
1024 |
879 //----------------------------------------------------------------------- |
1025 //----------------------------------------------------------------------- |
880 /** |
1026 /** |
881 * Appends a character literal to the formatter. |
1027 * Appends a character literal to the formatter. |
882 * <p> |
1028 * <p> |
883 * This character will be output during a print. |
1029 * This character will be output during a format. |
884 * |
1030 * |
885 * @param literal the literal to append, not null |
1031 * @param literal the literal to append, not null |
886 * @return this, for chaining, not null |
1032 * @return this, for chaining, not null |
887 */ |
1033 */ |
888 public DateTimeFormatterBuilder appendLiteral(char literal) { |
1034 public DateTimeFormatterBuilder appendLiteral(char literal) { |
1021 * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern |
1168 * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern |
1022 * letters, up to 9 digits. |
1169 * letters, up to 9 digits. |
1023 * <p> |
1170 * <p> |
1024 * <b>Year</b>: The count of letters determines the minimum field width below which padding is used. |
1171 * <b>Year</b>: The count of letters determines the minimum field width below which padding is used. |
1025 * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used. |
1172 * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used. |
1026 * For printing, this outputs the rightmost two digits. For parsing, this will parse using the |
1173 * For formatting, this outputs the rightmost two digits. For parsing, this will parse using the |
1027 * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. |
1174 * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. |
1028 * If the count of letters is less than four (but not two), then the sign is only output for negative |
1175 * If the count of letters is less than four (but not two), then the sign is only output for negative |
1029 * years as per {@link SignStyle#NORMAL}. |
1176 * years as per {@link SignStyle#NORMAL}. |
1030 * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} |
1177 * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} |
1031 * <p> |
1178 * <p> |
1032 * <b>ZoneId</b>: 'I' outputs the zone ID, such as 'Europe/Paris'. |
1179 * <b>ZoneId</b>: This outputs the time-zone ID, such as 'Europe/Paris'. |
1033 * <p> |
1180 * If the count of letters is two, then the time-zone ID is output. |
1034 * <b>Offset X</b>: This formats the offset using 'Z' when the offset is zero. |
1181 * Any other count of letters throws {@code IllegalArgumentException}. |
1035 * One letter outputs just the hour', such as '+01' |
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'. |
1036 * Two letters outputs the hour and minute, without a colon, such as '+0130'. |
1202 * Two letters outputs the hour and minute, without a colon, such as '+0130'. |
1037 * Three letters outputs the hour and minute, with a colon, such as '+01:30'. |
1203 * Three letters outputs the hour and minute, with a colon, such as '+01:30'. |
1038 * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. |
1204 * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. |
1039 * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. |
1205 * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. |
1040 * <p> |
1206 * Six or more letters throws {@code IllegalArgumentException}. |
1041 * <b>Offset Z</b>: This formats the offset using '+0000' or '+00:00' when the offset is zero. |
1207 * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero, |
1042 * One or two letters outputs the hour and minute, without a colon, such as '+0130'. |
1208 * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'. |
1043 * Three letters outputs the hour and minute, with a colon, such as '+01:30'. |
1209 * <pre> |
1044 * <p> |
1210 * Pattern Equivalent builder methods |
1045 * <b>Zone names</b>: Time zone names ('z') cannot be parsed. |
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> |
1046 * <p> |
1233 * <p> |
1047 * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()} |
1234 * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()} |
1048 * and {@link #optionalEnd()}. |
1235 * and {@link #optionalEnd()}. |
1049 * <p> |
1236 * <p> |
1050 * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces. |
1237 * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces. |
1056 * Any unrecognized letter is an error. |
1243 * Any unrecognized letter is an error. |
1057 * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. |
1244 * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. |
1058 * Despite this, it is recommended to use single quotes around all characters that you want to |
1245 * Despite this, it is recommended to use single quotes around all characters that you want to |
1059 * output directly to ensure that future changes do not break your application. |
1246 * output directly to ensure that future changes do not break your application. |
1060 * <p> |
1247 * <p> |
1061 * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}. |
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). |
1062 * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. |
1252 * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. |
1063 * Pattern letters 'Z' and 'X' are extended. |
1253 * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'. |
1064 * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. |
1254 * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. |
1065 * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. |
1255 * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. |
1066 * Number types will reject large numbers. |
1256 * Number types will reject large numbers. |
1067 * The pattern string is also similar, but not identical, to that defined by the |
|
1068 * Unicode Common Locale Data Repository (CLDR). |
|
1069 * |
1257 * |
1070 * @param pattern the pattern to add, not null |
1258 * @param pattern the pattern to add, not null |
1071 * @return this, for chaining, not null |
1259 * @return this, for chaining, not null |
1072 * @throws IllegalArgumentException if the pattern is invalid |
1260 * @throws IllegalArgumentException if the pattern is invalid |
1073 */ |
1261 */ |
1251 } |
1446 } |
1252 |
1447 |
1253 /** Map of letters to fields. */ |
1448 /** Map of letters to fields. */ |
1254 private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>(); |
1449 private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>(); |
1255 static { |
1450 static { |
1256 FIELD_MAP.put('G', ChronoField.ERA); // Java, CLDR (different to both for 1/2 chars) |
1451 FIELD_MAP.put('G', ChronoField.ERA); // Java, LDML (different to both for 1/2 chars) |
1257 FIELD_MAP.put('y', ChronoField.YEAR); // CLDR |
1452 FIELD_MAP.put('y', ChronoField.YEAR); // LDML |
1258 // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, CLDR // TODO redefine from above |
1453 // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, LDML // TODO redefine from above |
1259 // FIELD_MAP.put('u', ChronoField.YEAR); // CLDR // TODO |
1454 // FIELD_MAP.put('u', ChronoField.YEAR); // LDML // TODO |
1260 // FIELD_MAP.put('Y', ISODateTimeField.WEEK_BASED_YEAR); // Java7, CLDR (needs localized week number) // TODO |
1455 // FIELD_MAP.put('Y', IsoFields.WEEK_BASED_YEAR); // Java7, LDML (needs localized week number) // TODO |
1261 FIELD_MAP.put('Q', ISOFields.QUARTER_OF_YEAR); // CLDR (removed quarter from 310) |
1456 FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310) |
1262 FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, CLDR |
1457 FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, LDML |
1263 // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, CLDR (needs localized week number) |
1458 // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, LDML (needs localized week number) |
1264 // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, CLDR (needs localized week number) |
1459 // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, LDML (needs localized week number) |
1265 FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, CLDR |
1460 FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, LDML |
1266 FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, CLDR |
1461 FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, LDML |
1267 FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, CLDR |
1462 FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, LDML |
1268 FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, CLDR (different to both for 1/2 chars) |
1463 FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, LDML (different to both for 1/2 chars) |
1269 // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // CLDR (needs localized week number) |
1464 // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // LDML (needs localized week number) |
1270 FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, CLDR |
1465 FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, LDML |
1271 FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, CLDR |
1466 FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, LDML |
1272 FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, CLDR |
1467 FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, LDML |
1273 FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, CLDR |
1468 FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, LDML |
1274 FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, CLDR |
1469 FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, LDML |
1275 FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, CLDR |
1470 FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, LDML |
1276 FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, CLDR |
1471 FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, LDML |
1277 FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // CLDR (Java uses milli-of-second number) |
1472 FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (Java uses milli-of-second number) |
1278 FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // CLDR |
1473 FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML |
1279 FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 |
1474 FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML) |
1280 FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 |
1475 FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML) |
1281 // reserved - z,Z,X,I,p |
1476 // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4 |
1282 // Java - X - compatible, but extended to 4 and 5 letters |
1477 // 310 - Z - matches SimpleDateFormat and LDML |
1283 // Java - u - clashes with CLDR, go with CLDR (year-proleptic) here |
1478 // 310 - V - time-zone id, matches proposed LDML |
1284 // CLDR - U - cycle year name, not supported by 310 yet |
1479 // 310 - p - prefix for padding |
1285 // CLDR - l - deprecated |
1480 // 310 - X - matches proposed LDML, almost matches JavaSDF for 1, exact match 2&3, extended 4&5 |
1286 // CLDR - j - not relevant |
1481 // 310 - x - matches proposed LDML |
1287 // CLDR - g - modified-julian-day |
1482 // Java - u - clashes with LDML, go with LDML (year-proleptic) here |
1288 // CLDR - z - time-zone names // TODO properly |
1483 // LDML - U - cycle year name, not supported by 310 yet |
1289 // CLDR - Z - different approach here // TODO bring 310 in line with CLDR |
1484 // LDML - l - deprecated |
1290 // CLDR - v,V - extended time-zone names |
1485 // LDML - j - not relevant |
1291 // CLDR - q/c/L - standalone quarter/day-of-week/month |
1486 // LDML - g - modified-julian-day |
1292 // 310 - I - time-zone id |
1487 // LDML - v,V - extended time-zone names |
1293 // 310 - p - prefix for padding |
1488 // LDML - q/c/L - standalone quarter/day-of-week/month |
1294 } |
1489 } |
1295 |
1490 |
1296 //----------------------------------------------------------------------- |
1491 //----------------------------------------------------------------------- |
1297 /** |
1492 /** |
1298 * Causes the next added printer/parser to pad to a fixed width using a space. |
1493 * Causes the next added printer/parser to pad to a fixed width using a space. |
1299 * <p> |
1494 * <p> |
1300 * This padding will pad to a fixed width using spaces. |
1495 * This padding will pad to a fixed width using spaces. |
1301 * <p> |
1496 * <p> |
1302 * An exception will be thrown during printing if the pad width |
1497 * During formatting, the decorated element will be output and then padded |
1303 * is exceeded. |
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. |
1304 * |
1506 * |
1305 * @param padWidth the pad width, 1 or greater |
1507 * @param padWidth the pad width, 1 or greater |
1306 * @return this, for chaining, not null |
1508 * @return this, for chaining, not null |
1307 * @throws IllegalArgumentException if pad width is too small |
1509 * @throws IllegalArgumentException if pad width is too small |
1308 */ |
1510 */ |
1362 } |
1571 } |
1363 |
1572 |
1364 /** |
1573 /** |
1365 * Ends an optional section. |
1574 * Ends an optional section. |
1366 * <p> |
1575 * <p> |
1367 * The output of printing can include optional sections, which may be nested. |
1576 * The output of formatting can include optional sections, which may be nested. |
1368 * An optional section is started by calling {@link #optionalStart()} and ended |
1577 * An optional section is started by calling {@link #optionalStart()} and ended |
1369 * using this method (or at the end of the builder). |
1578 * using this method (or at the end of the builder). |
1370 * <p> |
1579 * <p> |
1371 * Calling this method without having previously called {@code optionalStart} |
1580 * Calling this method without having previously called {@code optionalStart} |
1372 * will throw an exception. |
1581 * will throw an exception. |
1373 * Calling this method immediately after calling {@code optionalStart} has no effect |
1582 * Calling this method immediately after calling {@code optionalStart} has no effect |
1374 * on the formatter other than ending the (empty) optional section. |
1583 * on the formatter other than ending the (empty) optional section. |
1375 * <p> |
1584 * <p> |
1376 * All elements in the optional section are treated as optional. |
1585 * All elements in the optional section are treated as optional. |
1377 * During printing, the section is only output if data is available in the |
1586 * During formatting, the section is only output if data is available in the |
1378 * {@code TemporalAccessor} for all the elements in the section. |
1587 * {@code TemporalAccessor} for all the elements in the section. |
1379 * During parsing, the whole section may be missing from the parsed string. |
1588 * During parsing, the whole section may be missing from the parsed string. |
1380 * <p> |
1589 * <p> |
1381 * For example, consider a builder setup as |
1590 * For example, consider a builder setup as |
1382 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. |
1591 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. |
1383 * During printing, the minute will only be output if its value can be obtained from the date-time. |
1592 * During formatting, the minute will only be output if its value can be obtained from the date-time. |
1384 * During parsing, the input will be successfully parsed whether the minute is present or not. |
1593 * During parsing, the input will be successfully parsed whether the minute is present or not. |
1385 * |
1594 * |
1386 * @return this, for chaining, not null |
1595 * @return this, for chaining, not null |
1387 * @throws IllegalStateException if there was no previous call to {@code optionalStart} |
1596 * @throws IllegalStateException if there was no previous call to {@code optionalStart} |
1388 */ |
1597 */ |
1464 return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null); |
1673 return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null); |
1465 } |
1674 } |
1466 |
1675 |
1467 //----------------------------------------------------------------------- |
1676 //----------------------------------------------------------------------- |
1468 /** |
1677 /** |
1469 * Strategy for printing/parsing date-time information. |
1678 * Strategy for formatting/parsing date-time information. |
1470 * <p> |
1679 * <p> |
1471 * The printer may print any part, or the whole, of the input date-time object. |
1680 * The printer may format any part, or the whole, of the input date-time object. |
1472 * Typically, a complete print is constructed from a number of smaller |
1681 * Typically, a complete format is constructed from a number of smaller |
1473 * units, each outputting a single field. |
1682 * units, each outputting a single field. |
1474 * <p> |
1683 * <p> |
1475 * The parser may parse any piece of text from the input, storing the result |
1684 * The parser may parse any piece of text from the input, storing the result |
1476 * in the context. Typically, each individual parser will just parse one |
1685 * in the context. Typically, each individual parser will just parse one |
1477 * field, such as the day-of-month, storing the value in the context. |
1686 * field, such as the day-of-month, storing the value in the context. |
1478 * Once the parse is complete, the caller will then convert the context |
1687 * Once the parse is complete, the caller will then resolve the parsed values |
1479 * to a {@link DateTimeBuilder} to merge the parsed values to create the |
1688 * to create the desired object, such as a {@code LocalDate}. |
1480 * desired object, such as a {@code LocalDate}. |
|
1481 * <p> |
1689 * <p> |
1482 * The parse position will be updated during the parse. Parsing will start at |
1690 * The parse position will be updated during the parse. Parsing will start at |
1483 * the specified index and the return value specifies the new parse position |
1691 * the specified index and the return value specifies the new parse position |
1484 * for the next parser. If an error occurs, the returned index will be negative |
1692 * for the next parser. If an error occurs, the returned index will be negative |
1485 * and will have the error position encoded using the complement operator. |
1693 * and will have the error position encoded using the complement operator. |
1638 this.padWidth = padWidth; |
1846 this.padWidth = padWidth; |
1639 this.padChar = padChar; |
1847 this.padChar = padChar; |
1640 } |
1848 } |
1641 |
1849 |
1642 @Override |
1850 @Override |
1643 public boolean print(DateTimePrintContext context, StringBuilder buf) { |
1851 public boolean format(DateTimePrintContext context, StringBuilder buf) { |
1644 int preLen = buf.length(); |
1852 int preLen = buf.length(); |
1645 if (printerParser.print(context, buf) == false) { |
1853 if (printerParser.format(context, buf) == false) { |
1646 return false; |
1854 return false; |
1647 } |
1855 } |
1648 int len = buf.length() - preLen; |
1856 int len = buf.length() - preLen; |
1649 if (len > padWidth) { |
1857 if (len > padWidth) { |
1650 throw new DateTimePrintException( |
1858 throw new DateTimeException( |
1651 "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth); |
1859 "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth); |
1652 } |
1860 } |
1653 for (int i = 0; i < padWidth - len; i++) { |
1861 for (int i = 0; i < padWidth - len; i++) { |
1654 buf.insert(preLen, padChar); |
1862 buf.insert(preLen, padChar); |
1655 } |
1863 } |
1656 return true; |
1864 return true; |
1657 } |
1865 } |
1658 |
1866 |
1659 @Override |
1867 @Override |
1660 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
1868 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
|
1869 // cache context before changed by decorated parser |
|
1870 final boolean strict = context.isStrict(); |
|
1871 // parse |
1661 if (position > text.length()) { |
1872 if (position > text.length()) { |
1662 throw new IndexOutOfBoundsException(); |
1873 throw new IndexOutOfBoundsException(); |
1663 } |
1874 } |
|
1875 if (position == text.length()) { |
|
1876 return ~position; // no more characters in the string |
|
1877 } |
1664 int endPos = position + padWidth; |
1878 int endPos = position + padWidth; |
1665 if (endPos > text.length()) { |
1879 if (endPos > text.length()) { |
1666 return ~position; // not enough characters in the string to meet the parse width |
1880 if (strict) { |
|
1881 return ~position; // not enough characters in the string to meet the parse width |
|
1882 } |
|
1883 endPos = text.length(); |
1667 } |
1884 } |
1668 int pos = position; |
1885 int pos = position; |
1669 while (pos < endPos && text.charAt(pos) == padChar) { |
1886 while (pos < endPos && context.charEquals(text.charAt(pos), padChar)) { |
1670 pos++; |
1887 pos++; |
1671 } |
1888 } |
1672 text = text.subSequence(0, endPos); |
1889 text = text.subSequence(0, endPos); |
1673 int firstError = 0; |
1890 int resultPos = printerParser.parse(context, text, pos); |
1674 while (pos >= position) { |
1891 if (resultPos != endPos && strict) { |
1675 int resultPos = printerParser.parse(context, text, pos); |
1892 return ~(position + pos); // parse of decorated field didn't parse to the end |
1676 if (resultPos < 0) { |
1893 } |
1677 // parse of decorated field had an error |
1894 return resultPos; |
1678 if (firstError == 0) { |
|
1679 firstError = resultPos; |
|
1680 } |
|
1681 // loop around in case the decorated parser can handle the padChar at the start |
|
1682 pos--; |
|
1683 continue; |
|
1684 } |
|
1685 if (resultPos != endPos) { |
|
1686 return ~position; // parse of decorated field didn't parse to the end |
|
1687 } |
|
1688 return resultPos; |
|
1689 } |
|
1690 // loop runs at least once, so firstError must be set by the time we get here |
|
1691 return firstError; // return error from first parse of decorated field |
|
1692 } |
1895 } |
1693 |
1896 |
1694 @Override |
1897 @Override |
1695 public String toString() { |
1898 public String toString() { |
1696 return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')"); |
1899 return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')"); |
2517 /** |
2735 /** |
2518 * Prints or parses an offset ID. |
2736 * Prints or parses an offset ID. |
2519 */ |
2737 */ |
2520 static final class OffsetIdPrinterParser implements DateTimePrinterParser { |
2738 static final class OffsetIdPrinterParser implements DateTimePrinterParser { |
2521 static final String[] PATTERNS = new String[] { |
2739 static final String[] PATTERNS = new String[] { |
2522 "+HH", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", |
2740 "+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", |
2523 }; // order used in pattern builder |
2741 }; // order used in pattern builder |
2524 static final OffsetIdPrinterParser INSTANCE_ID = new OffsetIdPrinterParser("Z", "+HH:MM:ss"); |
2742 static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z"); |
|
2743 static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0"); |
2525 |
2744 |
2526 private final String noOffsetText; |
2745 private final String noOffsetText; |
2527 private final int type; |
2746 private final int type; |
2528 |
2747 |
2529 /** |
2748 /** |
2530 * Constructor. |
2749 * Constructor. |
2531 * |
2750 * |
|
2751 * @param pattern the pattern |
2532 * @param noOffsetText the text to use for UTC, not null |
2752 * @param noOffsetText the text to use for UTC, not null |
2533 * @param pattern the pattern |
|
2534 */ |
2753 */ |
2535 OffsetIdPrinterParser(String noOffsetText, String pattern) { |
2754 OffsetIdPrinterParser(String pattern, String noOffsetText) { |
|
2755 Objects.requireNonNull(pattern, "pattern"); |
2536 Objects.requireNonNull(noOffsetText, "noOffsetText"); |
2756 Objects.requireNonNull(noOffsetText, "noOffsetText"); |
2537 Objects.requireNonNull(pattern, "pattern"); |
2757 this.type = checkPattern(pattern); |
2538 this.noOffsetText = noOffsetText; |
2758 this.noOffsetText = noOffsetText; |
2539 this.type = checkPattern(pattern); |
|
2540 } |
2759 } |
2541 |
2760 |
2542 private int checkPattern(String pattern) { |
2761 private int checkPattern(String pattern) { |
2543 for (int i = 0; i < PATTERNS.length; i++) { |
2762 for (int i = 0; i < PATTERNS.length; i++) { |
2544 if (PATTERNS[i].equals(pattern)) { |
2763 if (PATTERNS[i].equals(pattern)) { |
2579 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
2806 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
2580 int length = text.length(); |
2807 int length = text.length(); |
2581 int noOffsetLen = noOffsetText.length(); |
2808 int noOffsetLen = noOffsetText.length(); |
2582 if (noOffsetLen == 0) { |
2809 if (noOffsetLen == 0) { |
2583 if (position == length) { |
2810 if (position == length) { |
2584 context.setParsedField(OFFSET_SECONDS, 0); |
2811 return context.setParsedField(OFFSET_SECONDS, 0, position, position); |
2585 return position; |
|
2586 } |
2812 } |
2587 } else { |
2813 } else { |
2588 if (position == length) { |
2814 if (position == length) { |
2589 return ~position; |
2815 return ~position; |
2590 } |
2816 } |
2591 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) { |
2817 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) { |
2592 context.setParsedField(OFFSET_SECONDS, 0); |
2818 return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen); |
2593 return position + noOffsetLen; |
|
2594 } |
2819 } |
2595 } |
2820 } |
2596 |
2821 |
2597 // parse normal plus/minus offset |
2822 // parse normal plus/minus offset |
2598 char sign = text.charAt(position); // IOOBE if invalid position |
2823 char sign = text.charAt(position); // IOOBE if invalid position |
2599 if (sign == '+' || sign == '-') { |
2824 if (sign == '+' || sign == '-') { |
2600 // starts |
2825 // starts |
2601 int negative = (sign == '-' ? -1 : 1); |
2826 int negative = (sign == '-' ? -1 : 1); |
2602 int[] array = new int[4]; |
2827 int[] array = new int[4]; |
2603 array[0] = position + 1; |
2828 array[0] = position + 1; |
2604 if (parseNumber(array, 1, text, true) || |
2829 if ((parseNumber(array, 1, text, true) || |
2605 parseNumber(array, 2, text, type > 0) || |
2830 parseNumber(array, 2, text, type >=3) || |
2606 parseNumber(array, 3, text, false)) { |
2831 parseNumber(array, 3, text, false)) == false) { |
2607 return ~position; |
2832 // success |
2608 } |
2833 long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]); |
2609 long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]); |
2834 return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, array[0]); |
2610 context.setParsedField(OFFSET_SECONDS, offsetSecs); |
2835 } |
2611 return array[0]; |
2836 } |
2612 } else { |
2837 // handle special case of empty no offset text |
2613 // handle special case of empty no offset text |
2838 if (noOffsetLen == 0) { |
2614 if (noOffsetLen == 0) { |
2839 return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen); |
2615 context.setParsedField(OFFSET_SECONDS, 0); |
2840 } |
2616 return position + noOffsetLen; |
2841 return ~position; |
2617 } |
|
2618 return ~position; |
|
2619 } |
|
2620 } |
2842 } |
2621 |
2843 |
2622 /** |
2844 /** |
2623 * Parse a two digit zero-prefixed number. |
2845 * Parse a two digit zero-prefixed number. |
2624 * |
2846 * |
2657 } |
2879 } |
2658 |
2880 |
2659 @Override |
2881 @Override |
2660 public String toString() { |
2882 public String toString() { |
2661 String converted = noOffsetText.replace("'", "''"); |
2883 String converted = noOffsetText.replace("'", "''"); |
2662 return "Offset('" + converted + "'," + PATTERNS[type] + ")"; |
2884 return "Offset(" + PATTERNS[type] + ",'" + converted + "')"; |
2663 } |
2885 } |
2664 } |
2886 } |
2665 |
2887 |
2666 //----------------------------------------------------------------------- |
2888 //----------------------------------------------------------------------- |
2667 /** |
2889 /** |
2668 * Prints or parses a zone ID. |
2890 * Prints or parses a zone ID. |
2669 */ |
2891 */ |
2670 static final class ZoneTextPrinterParser implements DateTimePrinterParser { |
2892 static final class ZoneTextPrinterParser extends ZoneIdPrinterParser { |
2671 |
2893 |
2672 /** The text style to output. */ |
2894 /** The text style to output. */ |
2673 private final TextStyle textStyle; |
2895 private final TextStyle textStyle; |
2674 |
2896 |
2675 ZoneTextPrinterParser(TextStyle textStyle) { |
2897 /** The preferred zoneid map */ |
|
2898 private Set<String> preferredZones; |
|
2899 |
|
2900 ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) { |
|
2901 super(Queries.zone(), "ZoneText(" + textStyle + ")"); |
2676 this.textStyle = Objects.requireNonNull(textStyle, "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 } |
2677 } |
2909 } |
2678 |
2910 |
2679 private static final int STD = 0; |
2911 private static final int STD = 0; |
2680 private static final int DST = 1; |
2912 private static final int DST = 1; |
2681 private static final int GENERIC = 2; |
2913 private static final int GENERIC = 2; |
2682 |
|
2683 private static final Map<String, SoftReference<Map<Locale, String[]>>> cache = |
2914 private static final Map<String, SoftReference<Map<Locale, String[]>>> cache = |
2684 new ConcurrentHashMap<>(); |
2915 new ConcurrentHashMap<>(); |
2685 |
2916 |
2686 private static String getDisplayName(String id, int type, TextStyle style, Locale locale) { |
2917 private String getDisplayName(String id, int type, Locale locale) { |
2687 if (style == TextStyle.NARROW) { |
2918 if (textStyle == TextStyle.NARROW) { |
2688 return null; |
2919 return null; |
2689 } |
2920 } |
2690 String[] names; |
2921 String[] names; |
2691 SoftReference<Map<Locale, String[]>> ref = cache.get(id); |
2922 SoftReference<Map<Locale, String[]>> ref = cache.get(id); |
2692 Map<Locale, String[]> perLocale; |
2923 Map<Locale, String[]> perLocale = null; |
2693 if (ref == null || (perLocale = ref.get()) == null || |
2924 if (ref == null || (perLocale = ref.get()) == null || |
2694 (names = perLocale.get(locale)) == null) { |
2925 (names = perLocale.get(locale)) == null) { |
2695 names = TimeZoneNameUtility.retrieveDisplayNames(id, locale); |
2926 names = TimeZoneNameUtility.retrieveDisplayNames(id, locale); |
2696 if (names == null) { |
2927 if (names == null) { |
2697 return null; |
2928 return null; |
2705 names[6] = |
2936 names[6] = |
2706 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale); |
2937 TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale); |
2707 if (names[6] == null) { |
2938 if (names[6] == null) { |
2708 names[6] = names[0]; |
2939 names[6] = names[0]; |
2709 } |
2940 } |
2710 perLocale = new ConcurrentHashMap<>(); |
2941 if (perLocale == null) { |
|
2942 perLocale = new ConcurrentHashMap<>(); |
|
2943 } |
2711 perLocale.put(locale, names); |
2944 perLocale.put(locale, names); |
2712 ref = new SoftReference<>(perLocale); |
2945 cache.put(id, new SoftReference<>(perLocale)); |
2713 cache.put(id, ref); |
|
2714 } |
2946 } |
2715 switch (type) { |
2947 switch (type) { |
2716 case STD: |
2948 case STD: |
2717 return names[style.ordinal() + 1]; |
2949 return names[textStyle.ordinal() + 1]; |
2718 case DST: |
2950 case DST: |
2719 return names[style.ordinal() + 3]; |
2951 return names[textStyle.ordinal() + 3]; |
2720 } |
2952 } |
2721 return names[style.ordinal() + 5]; |
2953 return names[textStyle.ordinal() + 5]; |
2722 } |
2954 } |
2723 |
2955 |
2724 @Override |
2956 @Override |
2725 public boolean print(DateTimePrintContext context, StringBuilder buf) { |
2957 public boolean format(DateTimePrintContext context, StringBuilder buf) { |
2726 ZoneId zone = context.getValue(Queries.zoneId()); |
2958 ZoneId zone = context.getValue(Queries.zoneId()); |
2727 if (zone == null) { |
2959 if (zone == null) { |
2728 return false; |
2960 return false; |
2729 } |
2961 } |
2730 if (zone instanceof ZoneOffset) { |
2962 String zname = zone.getId(); |
2731 buf.append(zone.getId()); |
2963 if (!(zone instanceof ZoneOffset)) { |
2732 } else { |
|
2733 TemporalAccessor dt = context.getTemporal(); |
2964 TemporalAccessor dt = context.getTemporal(); |
2734 Instant instant = null; |
2965 String name = getDisplayName(zname, |
2735 if (dt.isSupported(ChronoField.INSTANT_SECONDS)) { |
2966 dt.isSupported(ChronoField.INSTANT_SECONDS) |
2736 instant = Instant.from(dt); |
2967 ? (zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD) |
2737 } |
2968 : GENERIC, |
2738 String name = getDisplayName(zone.getId(), |
2969 context.getLocale()); |
2739 instant == null ? GENERIC |
|
2740 : (zone.getRules().isDaylightSavings(instant) ? DST : STD), |
|
2741 textStyle, context.getLocale()); |
|
2742 if (name != null) { |
2970 if (name != null) { |
2743 buf.append(name); |
2971 zname = name; |
2744 } else { |
2972 } |
2745 buf.append(zone.getId()); |
2973 } |
2746 } |
2974 buf.append(zname); |
2747 } |
|
2748 return true; |
2975 return true; |
2749 } |
2976 } |
2750 |
2977 |
2751 @Override |
2978 // cache per instance for now |
2752 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
2979 private final Map<Locale, Entry<Integer, SoftReference<PrefixTree>>> |
2753 throw new UnsupportedOperationException(); |
2980 cachedTree = new HashMap<>(); |
2754 } |
2981 private final Map<Locale, Entry<Integer, SoftReference<PrefixTree>>> |
2755 |
2982 cachedTreeCI = new HashMap<>(); |
2756 @Override |
2983 |
2757 public String toString() { |
2984 @Override |
2758 return "ZoneText(" + textStyle + ")"; |
2985 protected PrefixTree getTree(DateTimeParseContext context) { |
|
2986 if (textStyle == TextStyle.NARROW) { |
|
2987 return super.getTree(context); |
|
2988 } |
|
2989 Locale locale = context.getLocale(); |
|
2990 boolean isCaseSensitive = context.isCaseSensitive(); |
|
2991 Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds(); |
|
2992 int regionIdsSize = regionIds.size(); |
|
2993 |
|
2994 Map<Locale, Entry<Integer, SoftReference<PrefixTree>>> cached = |
|
2995 isCaseSensitive ? cachedTree : cachedTreeCI; |
|
2996 |
|
2997 Entry<Integer, SoftReference<PrefixTree>> entry = null; |
|
2998 PrefixTree tree = null; |
|
2999 String[][] zoneStrings = null; |
|
3000 if ((entry = cached.get(locale)) == null || |
|
3001 (entry.getKey() != regionIdsSize || |
|
3002 (tree = entry.getValue().get()) == null)) { |
|
3003 tree = PrefixTree.newTree(context); |
|
3004 zoneStrings = TimeZoneNameUtility.getZoneStrings(locale); |
|
3005 for (String[] names : zoneStrings) { |
|
3006 String zid = names[0]; |
|
3007 if (!regionIds.contains(zid)) { |
|
3008 continue; |
|
3009 } |
|
3010 tree.add(zid, zid); // don't convert zid -> metazone |
|
3011 zid = ZoneName.toZid(zid, locale); |
|
3012 int i = textStyle == TextStyle.FULL ? 1 : 2; |
|
3013 for (; i < names.length; i += 2) { |
|
3014 tree.add(names[i], zid); |
|
3015 } |
|
3016 } |
|
3017 // if we have a set of preferred zones, need a copy and |
|
3018 // add the preferred zones again to overwrite |
|
3019 if (preferredZones != null) { |
|
3020 for (String[] names : zoneStrings) { |
|
3021 String zid = names[0]; |
|
3022 if (!preferredZones.contains(zid) || !regionIds.contains(zid)) { |
|
3023 continue; |
|
3024 } |
|
3025 int i = textStyle == TextStyle.FULL ? 1 : 2; |
|
3026 for (; i < names.length; i += 2) { |
|
3027 tree.add(names[i], zid); |
|
3028 } |
|
3029 } |
|
3030 } |
|
3031 cached.put(locale, new SimpleImmutableEntry<>(regionIdsSize, new SoftReference<>(tree))); |
|
3032 } |
|
3033 return tree; |
2759 } |
3034 } |
2760 } |
3035 } |
2761 |
3036 |
2762 //----------------------------------------------------------------------- |
3037 //----------------------------------------------------------------------- |
2763 /** |
3038 /** |
2764 * Prints or parses a zone ID. |
3039 * Prints or parses a zone ID. |
2765 */ |
3040 */ |
2766 static final class ZoneIdPrinterParser implements DateTimePrinterParser { |
3041 static class ZoneIdPrinterParser implements DateTimePrinterParser { |
2767 private final TemporalQuery<ZoneId> query; |
3042 private final TemporalQuery<ZoneId> query; |
2768 private final String description; |
3043 private final String description; |
2769 |
3044 |
2770 ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) { |
3045 ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) { |
2771 this.query = query; |
3046 this.query = query; |
2772 this.description = description; |
3047 this.description = description; |
2773 } |
3048 } |
2774 |
3049 |
2775 //----------------------------------------------------------------------- |
3050 @Override |
2776 @Override |
3051 public boolean format(DateTimePrintContext context, StringBuilder buf) { |
2777 public boolean print(DateTimePrintContext context, StringBuilder buf) { |
|
2778 ZoneId zone = context.getValue(query); |
3052 ZoneId zone = context.getValue(query); |
2779 if (zone == null) { |
3053 if (zone == null) { |
2780 return false; |
3054 return false; |
2781 } |
3055 } |
2782 buf.append(zone.getId()); |
3056 buf.append(zone.getId()); |
2783 return true; |
3057 return true; |
2784 } |
3058 } |
2785 |
3059 |
2786 //----------------------------------------------------------------------- |
|
2787 /** |
3060 /** |
2788 * The cached tree to speed up parsing. |
3061 * The cached tree to speed up parsing. |
2789 */ |
3062 */ |
2790 private static volatile Entry<Integer, PrefixTree> cachedPrefixTree; |
3063 private static volatile Entry<Integer, PrefixTree> cachedPrefixTree; |
2791 private static volatile Entry<Integer, PrefixTree> cachedPrefixTreeCI; |
3064 private static volatile Entry<Integer, PrefixTree> cachedPrefixTreeCI; |
2792 |
3065 |
2793 /** |
3066 protected PrefixTree getTree(DateTimeParseContext context) { |
2794 * This implementation looks for the longest matching string. |
|
2795 * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just |
|
2796 * Etc/GMC although both are valid. |
|
2797 */ |
|
2798 @Override |
|
2799 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
|
2800 int length = text.length(); |
|
2801 if (position > length) { |
|
2802 throw new IndexOutOfBoundsException(); |
|
2803 } |
|
2804 |
|
2805 // handle fixed time-zone IDs |
|
2806 if ((text.length() - position) >= 1) { |
|
2807 char nextChar = text.charAt(position); |
|
2808 if (nextChar == '+' || nextChar == '-') { |
|
2809 DateTimeParseContext newContext = context.copy(); |
|
2810 int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); |
|
2811 if (endPos < 0) { |
|
2812 return endPos; |
|
2813 } |
|
2814 int offset = (int) (long) newContext.getParsed(OFFSET_SECONDS); |
|
2815 ZoneId zone = ZoneOffset.ofTotalSeconds(offset); |
|
2816 context.setParsed(zone); |
|
2817 return endPos; |
|
2818 } |
|
2819 } |
|
2820 |
|
2821 // prepare parse tree |
3067 // prepare parse tree |
2822 Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds(); |
3068 Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds(); |
2823 final int regionIdsSize = regionIds.size(); |
3069 final int regionIdsSize = regionIds.size(); |
2824 Entry<Integer, PrefixTree> cached = context.isCaseSensitive() |
3070 Entry<Integer, PrefixTree> cached = context.isCaseSensitive() |
2825 ? cachedPrefixTree : cachedPrefixTreeCI; |
3071 ? cachedPrefixTree : cachedPrefixTreeCI; |
2826 if (cached == null || cached.getKey() != regionIdsSize) { |
3072 if (cached == null || cached.getKey() != regionIdsSize) { |
2827 synchronized (this) { |
3073 synchronized (this) { |
2828 cached = context.isCaseSensitive() ? cachedPrefixTree : cachedPrefixTreeCI; |
3074 cached = context.isCaseSensitive() ? cachedPrefixTree : cachedPrefixTreeCI; |
2829 if (cached == null || cached.getKey() != regionIdsSize) { |
3075 if (cached == null || cached.getKey() != regionIdsSize) { |
2830 cached = new SimpleImmutableEntry<>(regionIdsSize, |
3076 cached = new SimpleImmutableEntry<>(regionIdsSize, PrefixTree.newTree(regionIds, context)); |
2831 PrefixTree.newTree(regionIds, context.isCaseSensitive() |
|
2832 ? PrefixTree.STRICT : PrefixTree.CASE_INSENSITIVE)); |
|
2833 if (context.isCaseSensitive()) { |
3077 if (context.isCaseSensitive()) { |
2834 cachedPrefixTree = cached; |
3078 cachedPrefixTree = cached; |
2835 } else { |
3079 } else { |
2836 cachedPrefixTreeCI = cached; |
3080 cachedPrefixTreeCI = cached; |
2837 } |
3081 } |
2838 } |
3082 } |
2839 } |
3083 } |
2840 } |
3084 } |
2841 PrefixTree tree = cached.getValue(); |
3085 return cached.getValue(); |
|
3086 } |
|
3087 |
|
3088 /** |
|
3089 * This implementation looks for the longest matching string. |
|
3090 * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just |
|
3091 * Etc/GMC although both are valid. |
|
3092 */ |
|
3093 @Override |
|
3094 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
|
3095 int length = text.length(); |
|
3096 if (position > length) { |
|
3097 throw new IndexOutOfBoundsException(); |
|
3098 } |
|
3099 if (position == length) { |
|
3100 return ~position; |
|
3101 } |
|
3102 |
|
3103 // handle fixed time-zone IDs |
|
3104 char nextChar = text.charAt(position); |
|
3105 if (nextChar == '+' || nextChar == '-') { |
|
3106 return parseOffsetBased(context, text, position, OffsetIdPrinterParser.INSTANCE_ID_Z); |
|
3107 } else if (length >= position + 2) { |
|
3108 char nextNextChar = text.charAt(position + 1); |
|
3109 if (context.charEquals(nextChar, 'U') && context.charEquals(nextNextChar, 'T')) { |
|
3110 if (length >= position + 3 && context.charEquals(text.charAt(position + 2), 'C')) { |
|
3111 return parseOffsetBased(context, text, position + 3, OffsetIdPrinterParser.INSTANCE_ID_ZERO); |
|
3112 } |
|
3113 return parseOffsetBased(context, text, position + 2, OffsetIdPrinterParser.INSTANCE_ID_ZERO); |
|
3114 } else if (context.charEquals(nextChar, 'G') && length >= position + 3 && |
|
3115 context.charEquals(nextNextChar, 'M') && context.charEquals(text.charAt(position + 2), 'T')) { |
|
3116 return parseOffsetBased(context, text, position + 3, OffsetIdPrinterParser.INSTANCE_ID_ZERO); |
|
3117 } |
|
3118 } |
2842 |
3119 |
2843 // parse |
3120 // parse |
2844 String parsedZoneId = tree.match(text, position, length); |
3121 PrefixTree tree = getTree(context); |
2845 if (parsedZoneId == null || regionIds.contains(parsedZoneId) == false) { |
3122 ParsePosition ppos = new ParsePosition(position); |
2846 if (text.charAt(position) == 'Z') { |
3123 String parsedZoneId = tree.match(text, ppos); |
|
3124 if (parsedZoneId == null) { |
|
3125 if (context.charEquals(nextChar, 'Z')) { |
2847 context.setParsed(ZoneOffset.UTC); |
3126 context.setParsed(ZoneOffset.UTC); |
2848 return position + 1; |
3127 return position + 1; |
2849 } |
3128 } |
2850 return ~position; |
3129 return ~position; |
2851 } |
3130 } |
2852 context.setParsed(ZoneId.of(parsedZoneId)); |
3131 context.setParsed(ZoneId.of(parsedZoneId)); |
2853 return position + parsedZoneId.length(); |
3132 return ppos.getIndex(); |
2854 } |
3133 } |
2855 |
3134 |
|
3135 private int parseOffsetBased(DateTimeParseContext context, CharSequence text, int position, OffsetIdPrinterParser parser) { |
|
3136 DateTimeParseContext newContext = context.copy(); |
|
3137 int endPos = parser.parse(newContext, text, position); |
|
3138 if (endPos < 0) { |
|
3139 if (parser == OffsetIdPrinterParser.INSTANCE_ID_Z) { |
|
3140 return ~position; |
|
3141 } |
|
3142 context.setParsed(ZoneOffset.UTC); |
|
3143 return position; |
|
3144 } |
|
3145 int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue(); |
|
3146 ZoneId zone = ZoneOffset.ofTotalSeconds(offset); |
|
3147 context.setParsed(zone); |
|
3148 return endPos; |
|
3149 } |
2856 |
3150 |
2857 @Override |
3151 @Override |
2858 public String toString() { |
3152 public String toString() { |
2859 return description; |
3153 return description; |
2860 } |
3154 } |
2886 c0 = key.charAt(0); |
3176 c0 = key.charAt(0); |
2887 } |
3177 } |
2888 } |
3178 } |
2889 |
3179 |
2890 /** |
3180 /** |
2891 * Creates a new prefix parsing tree. |
3181 * Creates a new prefix parsing tree based on parse context. |
2892 * |
3182 * |
2893 * @param type the type of the prefix tree. One of the three supported |
3183 * @param context the parse context |
2894 * types, STRICT, CASE_INSENSITIVE and LENIENT |
|
2895 * @return the tree, not null |
3184 * @return the tree, not null |
2896 */ |
3185 */ |
2897 public static PrefixTree newTree(int type) { |
3186 public static PrefixTree newTree(DateTimeParseContext context) { |
2898 PrefixTree tree; |
3187 //if (!context.isStrict()) { |
2899 switch(type) { |
3188 // return new LENIENT("", null, null); |
2900 case STRICT: |
3189 //} |
2901 tree = new PrefixTree("", null, null); |
3190 if (context.isCaseSensitive()) { |
2902 break; |
3191 return new PrefixTree("", null, null); |
2903 case CASE_INSENSITIVE: |
3192 } |
2904 tree = new CI("", null, null); |
3193 return new CI("", null, null); |
2905 break; |
|
2906 case LENIENT: |
|
2907 tree = new LENIENT("", null, null); |
|
2908 break; |
|
2909 default: |
|
2910 throw new IllegalArgumentException("Unknown type"); |
|
2911 } |
|
2912 return tree; |
|
2913 } |
3194 } |
2914 |
3195 |
2915 /** |
3196 /** |
2916 * Creates a new prefix parsing tree. |
3197 * Creates a new prefix parsing tree. |
2917 * |
3198 * |
2918 * @param keys a set of strings to build the prefix parsing tree, not null |
3199 * @param keys a set of strings to build the prefix parsing tree, not null |
2919 * @param type the type of the prefix tree. One of the three supported |
3200 * @param context the parse context |
2920 * types, STRICT, CASE_INSENSITIVE and LENIENT |
|
2921 * @return the tree, not null |
3201 * @return the tree, not null |
2922 */ |
3202 */ |
2923 public static PrefixTree newTree(Set<String> keys, int type) { |
3203 public static PrefixTree newTree(Set<String> keys, DateTimeParseContext context) { |
2924 PrefixTree tree = newTree(type); |
3204 PrefixTree tree = newTree(context); |
2925 for (String k : keys) { |
3205 for (String k : keys) { |
2926 tree.add0(k, k); |
3206 tree.add0(k, k); |
2927 } |
3207 } |
2928 return tree; |
3208 return tree; |
2929 } |
3209 } |
|
3210 |
|
3211 /** |
|
3212 * Clone a copy of this tree |
|
3213 */ |
|
3214 public PrefixTree copyTree() { |
|
3215 PrefixTree copy = new PrefixTree(key, value, null); |
|
3216 if (child != null) { |
|
3217 copy.child = child.copyTree(); |
|
3218 } |
|
3219 if (sibling != null) { |
|
3220 copy.sibling = sibling.copyTree(); |
|
3221 } |
|
3222 return copy; |
|
3223 } |
|
3224 |
2930 |
3225 |
2931 /** |
3226 /** |
2932 * Adds a pair of {key, value} into the prefix tree. |
3227 * Adds a pair of {key, value} into the prefix tree. |
2933 * |
3228 * |
2934 * @param k the key, not null |
3229 * @param k the key, not null |
3226 return true; |
3519 return true; |
3227 } |
3520 } |
3228 |
3521 |
3229 @Override |
3522 @Override |
3230 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
3523 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
3231 return ~position; // TODO, including case insensitive |
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; |
3232 } |
3544 } |
3233 } |
3545 } |
3234 |
3546 |
3235 //----------------------------------------------------------------------- |
3547 //----------------------------------------------------------------------- |
3236 /** |
3548 /** |
3237 * Prints or parses a localized pattern. |
3549 * Prints or parses a localized pattern. |
3238 */ |
3550 */ |
3239 static final class LocalizedPrinterParser implements DateTimePrinterParser { |
3551 static final class LocalizedPrinterParser implements DateTimePrinterParser { |
3240 private final FormatStyle dateStyle; |
3552 private final FormatStyle dateStyle; |
3241 private final FormatStyle timeStyle; |
3553 private final FormatStyle timeStyle; |
3242 private final Chrono<?> chrono; |
|
3243 |
3554 |
3244 /** |
3555 /** |
3245 * Constructor. |
3556 * Constructor. |
3246 * |
3557 * |
3247 * @param dateStyle the date style to use, may be null |
3558 * @param dateStyle the date style to use, may be null |
3248 * @param timeStyle the time style to use, may be null |
3559 * @param timeStyle the time style to use, may be null |
3249 * @param chrono the chronology to use, not null |
|
3250 */ |
3560 */ |
3251 LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) { |
3561 LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) { |
3252 // validated by caller |
3562 // validated by caller |
3253 this.dateStyle = dateStyle; |
3563 this.dateStyle = dateStyle; |
3254 this.timeStyle = timeStyle; |
3564 this.timeStyle = timeStyle; |
3255 this.chrono = chrono; |
3565 } |
3256 } |
3566 |
3257 |
3567 @Override |
3258 @Override |
3568 public boolean format(DateTimePrintContext context, StringBuilder buf) { |
3259 public boolean print(DateTimePrintContext context, StringBuilder buf) { |
3569 Chronology chrono = Chronology.from(context.getTemporal()); |
3260 return formatter(context.getLocale()).toPrinterParser(false).print(context, buf); |
3570 return formatter(context.getLocale(), chrono).toPrinterParser(false).format(context, buf); |
3261 } |
3571 } |
3262 |
3572 |
3263 @Override |
3573 @Override |
3264 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
3574 public int parse(DateTimeParseContext context, CharSequence text, int position) { |
3265 return formatter(context.getLocale()).toPrinterParser(false).parse(context, text, position); |
3575 Chronology chrono = context.getEffectiveChronology(); |
|
3576 return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position); |
3266 } |
3577 } |
3267 |
3578 |
3268 /** |
3579 /** |
3269 * Gets the formatter to use. |
3580 * Gets the formatter to use. |
3270 * |
3581 * |
3271 * @param locale the locale to use, not null |
3582 * @param locale the locale to use, not null |
|
3583 * @param chrono the chronology to use, not null |
3272 * @return the formatter, not null |
3584 * @return the formatter, not null |
3273 * @throws IllegalArgumentException if the formatter cannot be found |
3585 * @throws IllegalArgumentException if the formatter cannot be found |
3274 */ |
3586 */ |
3275 private DateTimeFormatter formatter(Locale locale) { |
3587 private DateTimeFormatter formatter(Locale locale, Chronology chrono) { |
3276 return DateTimeFormatStyleProvider.getInstance() |
3588 return DateTimeFormatStyleProvider.getInstance() |
3277 .getFormatter(dateStyle, timeStyle, chrono, locale); |
3589 .getFormatter(dateStyle, timeStyle, chrono, locale); |
3278 } |
3590 } |
3279 |
3591 |
3280 @Override |
3592 @Override |
3281 public String toString() { |
3593 public String toString() { |
3282 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + |
3594 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + |
3283 (timeStyle != null ? timeStyle : "") + "," + chrono.getId() + ")"; |
3595 (timeStyle != null ? timeStyle : "") + ")"; |
3284 } |
3596 } |
3285 } |
3597 } |
3286 |
3598 |
3287 |
3599 |
3288 //----------------------------------------------------------------------- |
3600 //----------------------------------------------------------------------- |