1757 appendLocalizedOffset(TextStyle.FULL); 1758 } else { 1759 throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur); 1760 } 1761 } else if (cur == 'X') { 1762 if (count > 5) { 1763 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1764 } 1765 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z"); 1766 } else if (cur == 'x') { 1767 if (count > 5) { 1768 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1769 } 1770 String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00")); 1771 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero); 1772 } else if (cur == 'W') { 1773 // Fields defined by Locale 1774 if (count > 1) { 1775 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1776 } 1777 appendInternal(new WeekBasedFieldPrinterParser(cur, count)); 1778 } else if (cur == 'w') { 1779 // Fields defined by Locale 1780 if (count > 2) { 1781 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1782 } 1783 appendInternal(new WeekBasedFieldPrinterParser(cur, count)); 1784 } else if (cur == 'Y') { 1785 // Fields defined by Locale 1786 appendInternal(new WeekBasedFieldPrinterParser(cur, count)); 1787 } else { 1788 throw new IllegalArgumentException("Unknown pattern letter: " + cur); 1789 } 1790 pos--; 1791 1792 } else if (cur == '\'') { 1793 // parse literals 1794 int start = pos++; 1795 for ( ; pos < pattern.length(); pos++) { 1796 if (pattern.charAt(pos) == '\'') { 1797 if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') { 1798 pos++; 1799 } else { 1800 break; // end of literal 1801 } 1802 } 1803 } 1804 if (pos >= pattern.length()) { 1805 throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern); 1806 } 1826 appendLiteral(cur); 1827 } 1828 } 1829 } 1830 1831 @SuppressWarnings("fallthrough") 1832 private void parseField(char cur, int count, TemporalField field) { 1833 boolean standalone = false; 1834 switch (cur) { 1835 case 'u': 1836 case 'y': 1837 if (count == 2) { 1838 appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE); 1839 } else if (count < 4) { 1840 appendValue(field, count, 19, SignStyle.NORMAL); 1841 } else { 1842 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); 1843 } 1844 break; 1845 case 'c': 1846 if (count == 2) { 1847 throw new IllegalArgumentException("Invalid pattern \"cc\""); 1848 } 1849 /*fallthrough*/ 1850 case 'L': 1851 case 'q': 1852 standalone = true; 1853 /*fallthrough*/ 1854 case 'M': 1855 case 'Q': 1856 case 'E': 1857 case 'e': 1858 switch (count) { 1859 case 1: 1860 case 2: 1861 if (cur == 'c' || cur == 'e') { 1862 appendInternal(new WeekBasedFieldPrinterParser(cur, count)); 1863 } else if (cur == 'E') { 1864 appendText(field, TextStyle.SHORT); 1865 } else { 1866 if (count == 1) { 1867 appendValue(field); 1868 } else { 1869 appendValue(field, 2); 1870 } 1871 } 1872 break; 1873 case 3: 1874 appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT); 1875 break; 1876 case 4: 1877 appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL); 1878 break; 1879 case 5: 1880 appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW); 1881 break; 1882 default: 4754 } 4755 } 4756 return formatter; 4757 } 4758 4759 @Override 4760 public String toString() { 4761 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + 4762 (timeStyle != null ? timeStyle : "") + ")"; 4763 } 4764 } 4765 4766 //----------------------------------------------------------------------- 4767 /** 4768 * Prints or parses a localized pattern from a localized field. 4769 * The specific formatter and parameters is not selected until the 4770 * the field is to be printed or parsed. 4771 * The locale is needed to select the proper WeekFields from which 4772 * the field for day-of-week, week-of-month, or week-of-year is selected. 4773 */ 4774 static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser { 4775 private char chr; 4776 private int count; 4777 4778 /** 4779 * Constructor. 4780 * 4781 * @param chr the pattern format letter that added this PrinterParser. 4782 * @param count the repeat count of the format letter 4783 */ 4784 WeekBasedFieldPrinterParser(char chr, int count) { 4785 this.chr = chr; 4786 this.count = count; 4787 } 4788 4789 @Override 4790 public boolean format(DateTimePrintContext context, StringBuilder buf) { 4791 return printerParser(context.getLocale()).format(context, buf); 4792 } 4793 4794 @Override 4795 public int parse(DateTimeParseContext context, CharSequence text, int position) { 4796 return printerParser(context.getLocale()).parse(context, text, position); 4797 } 4798 4799 /** 4800 * Gets the printerParser to use based on the field and the locale. 4801 * 4802 * @param locale the locale to use, not null 4803 * @return the formatter, not null 4804 * @throws IllegalArgumentException if the formatter cannot be found 4805 */ 4806 private DateTimePrinterParser printerParser(Locale locale) { 4807 WeekFields weekDef = WeekFields.of(locale); 4808 TemporalField field = null; 4809 switch (chr) { 4810 case 'Y': 4811 field = weekDef.weekBasedYear(); 4812 if (count == 2) { 4813 return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 0); 4814 } else { 4815 return new NumberPrinterParser(field, count, 19, 4816 (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1); 4817 } 4818 case 'e': 4819 case 'c': 4820 field = weekDef.dayOfWeek(); 4821 break; 4822 case 'w': 4823 field = weekDef.weekOfWeekBasedYear(); 4824 break; 4825 case 'W': 4826 field = weekDef.weekOfMonth(); 4827 break; 4828 default: 4829 throw new IllegalStateException("unreachable"); 4830 } 4831 return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE); 4832 } 4833 4834 @Override 4835 public String toString() { 4836 StringBuilder sb = new StringBuilder(30); 4837 sb.append("Localized("); 4838 if (chr == 'Y') { 4839 if (count == 1) { 4840 sb.append("WeekBasedYear"); 4841 } else if (count == 2) { 4842 sb.append("ReducedValue(WeekBasedYear,2,2,2000-01-01)"); 4843 } else { 4844 sb.append("WeekBasedYear,").append(count).append(",") 4845 .append(19).append(",") 4846 .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD); 4847 } 4848 } else { 4849 switch (chr) { 4850 case 'c': 4851 case 'e': | 1757 appendLocalizedOffset(TextStyle.FULL); 1758 } else { 1759 throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur); 1760 } 1761 } else if (cur == 'X') { 1762 if (count > 5) { 1763 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1764 } 1765 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z"); 1766 } else if (cur == 'x') { 1767 if (count > 5) { 1768 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1769 } 1770 String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00")); 1771 appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero); 1772 } else if (cur == 'W') { 1773 // Fields defined by Locale 1774 if (count > 1) { 1775 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1776 } 1777 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 1778 } else if (cur == 'w') { 1779 // Fields defined by Locale 1780 if (count > 2) { 1781 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1782 } 1783 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); 1784 } else if (cur == 'Y') { 1785 // Fields defined by Locale 1786 if (count == 2) { 1787 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); 1788 } else { 1789 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19)); 1790 } 1791 } else { 1792 throw new IllegalArgumentException("Unknown pattern letter: " + cur); 1793 } 1794 pos--; 1795 1796 } else if (cur == '\'') { 1797 // parse literals 1798 int start = pos++; 1799 for ( ; pos < pattern.length(); pos++) { 1800 if (pattern.charAt(pos) == '\'') { 1801 if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') { 1802 pos++; 1803 } else { 1804 break; // end of literal 1805 } 1806 } 1807 } 1808 if (pos >= pattern.length()) { 1809 throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern); 1810 } 1830 appendLiteral(cur); 1831 } 1832 } 1833 } 1834 1835 @SuppressWarnings("fallthrough") 1836 private void parseField(char cur, int count, TemporalField field) { 1837 boolean standalone = false; 1838 switch (cur) { 1839 case 'u': 1840 case 'y': 1841 if (count == 2) { 1842 appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE); 1843 } else if (count < 4) { 1844 appendValue(field, count, 19, SignStyle.NORMAL); 1845 } else { 1846 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); 1847 } 1848 break; 1849 case 'c': 1850 if (count == 1) { 1851 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 1852 break; 1853 } else if (count == 2) { 1854 throw new IllegalArgumentException("Invalid pattern \"cc\""); 1855 } 1856 /*fallthrough*/ 1857 case 'L': 1858 case 'q': 1859 standalone = true; 1860 /*fallthrough*/ 1861 case 'M': 1862 case 'Q': 1863 case 'E': 1864 case 'e': 1865 switch (count) { 1866 case 1: 1867 case 2: 1868 if (cur == 'e') { 1869 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); 1870 } else if (cur == 'E') { 1871 appendText(field, TextStyle.SHORT); 1872 } else { 1873 if (count == 1) { 1874 appendValue(field); 1875 } else { 1876 appendValue(field, 2); 1877 } 1878 } 1879 break; 1880 case 3: 1881 appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT); 1882 break; 1883 case 4: 1884 appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL); 1885 break; 1886 case 5: 1887 appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW); 1888 break; 1889 default: 4761 } 4762 } 4763 return formatter; 4764 } 4765 4766 @Override 4767 public String toString() { 4768 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + 4769 (timeStyle != null ? timeStyle : "") + ")"; 4770 } 4771 } 4772 4773 //----------------------------------------------------------------------- 4774 /** 4775 * Prints or parses a localized pattern from a localized field. 4776 * The specific formatter and parameters is not selected until the 4777 * the field is to be printed or parsed. 4778 * The locale is needed to select the proper WeekFields from which 4779 * the field for day-of-week, week-of-month, or week-of-year is selected. 4780 */ 4781 static final class WeekBasedFieldPrinterParser extends NumberPrinterParser { 4782 private char chr; 4783 private int count; 4784 4785 /** 4786 * Constructor. 4787 * 4788 * @param chr the pattern format letter that added this PrinterParser. 4789 * @param count the repeat count of the format letter 4790 * @param minWidth the minimum field width, from 1 to 19 4791 * @param maxWidth the maximum field width, from minWidth to 19 4792 */ 4793 WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) { 4794 this(chr, count, minWidth, maxWidth, 0); 4795 } 4796 4797 /** 4798 * Constructor. 4799 * 4800 * @param chr the pattern format letter that added this PrinterParser. 4801 * @param count the repeat count of the format letter 4802 * @param minWidth the minimum field width, from 1 to 19 4803 * @param maxWidth the maximum field width, from minWidth to 19 4804 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater, 4805 * -1 if fixed width due to active adjacent parsing 4806 */ 4807 WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth, 4808 int subsequentWidth) { 4809 super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth); 4810 this.chr = chr; 4811 this.count = count; 4812 } 4813 4814 /** 4815 * Returns a new instance with fixed width flag set. 4816 * 4817 * @return a new updated printer-parser, not null 4818 */ 4819 @Override 4820 WeekBasedFieldPrinterParser withFixedWidth() { 4821 if (subsequentWidth == -1) { 4822 return this; 4823 } 4824 return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, -1); 4825 } 4826 4827 /** 4828 * Returns a new instance with an updated subsequent width. 4829 * 4830 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater 4831 * @return a new updated printer-parser, not null 4832 */ 4833 @Override 4834 WeekBasedFieldPrinterParser withSubsequentWidth(int subsequentWidth) { 4835 return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, 4836 this.subsequentWidth + subsequentWidth); 4837 } 4838 4839 @Override 4840 public boolean format(DateTimePrintContext context, StringBuilder buf) { 4841 return printerParser(context.getLocale()).format(context, buf); 4842 } 4843 4844 @Override 4845 public int parse(DateTimeParseContext context, CharSequence text, int position) { 4846 return printerParser(context.getLocale()).parse(context, text, position); 4847 } 4848 4849 /** 4850 * Gets the printerParser to use based on the field and the locale. 4851 * 4852 * @param locale the locale to use, not null 4853 * @return the formatter, not null 4854 * @throws IllegalArgumentException if the formatter cannot be found 4855 */ 4856 private DateTimePrinterParser printerParser(Locale locale) { 4857 WeekFields weekDef = WeekFields.of(locale); 4858 TemporalField field = null; 4859 switch (chr) { 4860 case 'Y': 4861 field = weekDef.weekBasedYear(); 4862 if (count == 2) { 4863 return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 4864 this.subsequentWidth); 4865 } else { 4866 return new NumberPrinterParser(field, count, 19, 4867 (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, 4868 this.subsequentWidth); 4869 } 4870 case 'e': 4871 case 'c': 4872 field = weekDef.dayOfWeek(); 4873 break; 4874 case 'w': 4875 field = weekDef.weekOfWeekBasedYear(); 4876 break; 4877 case 'W': 4878 field = weekDef.weekOfMonth(); 4879 break; 4880 default: 4881 throw new IllegalStateException("unreachable"); 4882 } 4883 return new NumberPrinterParser(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, 4884 this.subsequentWidth); 4885 } 4886 4887 @Override 4888 public String toString() { 4889 StringBuilder sb = new StringBuilder(30); 4890 sb.append("Localized("); 4891 if (chr == 'Y') { 4892 if (count == 1) { 4893 sb.append("WeekBasedYear"); 4894 } else if (count == 2) { 4895 sb.append("ReducedValue(WeekBasedYear,2,2,2000-01-01)"); 4896 } else { 4897 sb.append("WeekBasedYear,").append(count).append(",") 4898 .append(19).append(",") 4899 .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD); 4900 } 4901 } else { 4902 switch (chr) { 4903 case 'c': 4904 case 'e': |