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, 2)); 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 */ 4791 WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) { 4792 this(chr, count, minWidth, maxWidth, 0); 4793 } 4794 4795 WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth, 4796 int subsequentWidth) { 4797 super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth); 4798 this.chr = chr; 4799 this.count = count; 4800 } 4801 4802 /** 4803 * Returns a new instance with fixed width flag set. 4804 * 4805 * @return a new updated printer-parser, not null 4806 */ 4807 @Override 4808 WeekBasedFieldPrinterParser withFixedWidth() { 4809 if (subsequentWidth == -1) { 4810 return this; 4811 } 4812 return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, -1); 4813 } 4814 4815 /** 4816 * Returns a new instance with an updated subsequent width. 4817 * 4818 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater 4819 * @return a new updated printer-parser, not null 4820 */ 4821 @Override 4822 WeekBasedFieldPrinterParser withSubsequentWidth(int subsequentWidth) { 4823 return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, 4824 this.subsequentWidth + subsequentWidth); 4825 } 4826 4827 @Override 4828 public boolean format(DateTimePrintContext context, StringBuilder buf) { 4829 return printerParser(context.getLocale()).format(context, buf); 4830 } 4831 4832 @Override 4833 public int parse(DateTimeParseContext context, CharSequence text, int position) { 4834 return printerParser(context.getLocale()).parse(context, text, position); 4835 } 4836 4837 /** 4838 * Gets the printerParser to use based on the field and the locale. 4839 * 4840 * @param locale the locale to use, not null 4841 * @return the formatter, not null 4842 * @throws IllegalArgumentException if the formatter cannot be found 4843 */ 4844 private DateTimePrinterParser printerParser(Locale locale) { 4845 WeekFields weekDef = WeekFields.of(locale); 4846 TemporalField field = null; 4847 switch (chr) { 4848 case 'Y': 4849 field = weekDef.weekBasedYear(); 4850 if (count == 2) { 4851 return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 4852 this.subsequentWidth); 4853 } else { 4854 return new NumberPrinterParser(field, count, 19, 4855 (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, 4856 this.subsequentWidth); 4857 } 4858 case 'e': 4859 case 'c': 4860 field = weekDef.dayOfWeek(); 4861 break; 4862 case 'w': 4863 field = weekDef.weekOfWeekBasedYear(); 4864 break; 4865 case 'W': 4866 field = weekDef.weekOfMonth(); 4867 break; 4868 default: 4869 throw new IllegalStateException("unreachable"); 4870 } 4871 return new NumberPrinterParser(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, this.subsequentWidth); 4872 } 4873 4874 @Override 4875 public String toString() { 4876 StringBuilder sb = new StringBuilder(30); 4877 sb.append("Localized("); 4878 if (chr == 'Y') { 4879 if (count == 1) { 4880 sb.append("WeekBasedYear"); 4881 } else if (count == 2) { 4882 sb.append("ReducedValue(WeekBasedYear,2,2,2000-01-01)"); 4883 } else { 4884 sb.append("WeekBasedYear,").append(count).append(",") 4885 .append(19).append(",") 4886 .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD); 4887 } 4888 } else { 4889 switch (chr) { 4890 case 'c': 4891 case 'e': |