# HG changeset patch # User ntv # Date 1482345934 0 # Node ID 0d68b06248fb451cf933dc0d2174461be1e5f211 # Parent 691c320b44f7b954ff48378503c793105195bcaf 8145633: Adjacent value parsing not supported for Localized Patterns Summary: Enhance the localized weekfields to take part in adjacent value parsing Reviewed-by: rriggs, scolebourne diff -r 691c320b44f7 -r 0d68b06248fb jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java --- a/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java Wed Dec 21 07:49:36 2016 -0800 +++ b/jdk/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java Wed Dec 21 18:45:34 2016 +0000 @@ -1774,16 +1774,20 @@ if (count > 1) { throw new IllegalArgumentException("Too many pattern letters: " + cur); } - appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); } else if (cur == 'w') { // Fields defined by Locale if (count > 2) { throw new IllegalArgumentException("Too many pattern letters: " + cur); } - appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); } else if (cur == 'Y') { // Fields defined by Locale - appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + if (count == 2) { + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2)); + } else { + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19)); + } } else { throw new IllegalArgumentException("Unknown pattern letter: " + cur); } @@ -1843,7 +1847,10 @@ } break; case 'c': - if (count == 2) { + if (count == 1) { + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); + break; + } else if (count == 2) { throw new IllegalArgumentException("Invalid pattern \"cc\""); } /*fallthrough*/ @@ -1858,8 +1865,8 @@ switch (count) { case 1: case 2: - if (cur == 'c' || cur == 'e') { - appendInternal(new WeekBasedFieldPrinterParser(cur, count)); + if (cur == 'e') { + appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count)); } else if (cur == 'E') { appendText(field, TextStyle.SHORT); } else { @@ -4770,8 +4777,9 @@ * the field is to be printed or parsed. * The locale is needed to select the proper WeekFields from which * the field for day-of-week, week-of-month, or week-of-year is selected. + * Hence the inherited field NumberPrinterParser.field is unused. */ - static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser { + static final class WeekBasedFieldPrinterParser extends NumberPrinterParser { private char chr; private int count; @@ -4780,12 +4788,55 @@ * * @param chr the pattern format letter that added this PrinterParser. * @param count the repeat count of the format letter + * @param minWidth the minimum field width, from 1 to 19 + * @param maxWidth the maximum field width, from minWidth to 19 */ - WeekBasedFieldPrinterParser(char chr, int count) { + WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) { + this(chr, count, minWidth, maxWidth, 0); + } + + /** + * Constructor. + * + * @param chr the pattern format letter that added this PrinterParser. + * @param count the repeat count of the format letter + * @param minWidth the minimum field width, from 1 to 19 + * @param maxWidth the maximum field width, from minWidth to 19 + * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater, + * -1 if fixed width due to active adjacent parsing + */ + WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth, + int subsequentWidth) { + super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth); this.chr = chr; this.count = count; } + /** + * Returns a new instance with fixed width flag set. + * + * @return a new updated printer-parser, not null + */ + @Override + WeekBasedFieldPrinterParser withFixedWidth() { + if (subsequentWidth == -1) { + return this; + } + return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, -1); + } + + /** + * Returns a new instance with an updated subsequent width. + * + * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater + * @return a new updated printer-parser, not null + */ + @Override + WeekBasedFieldPrinterParser withSubsequentWidth(int subsequentWidth) { + return new WeekBasedFieldPrinterParser(chr, count, minWidth, maxWidth, + this.subsequentWidth + subsequentWidth); + } + @Override public boolean format(DateTimePrintContext context, StringBuilder buf) { return printerParser(context.getLocale()).format(context, buf); @@ -4810,10 +4861,12 @@ case 'Y': field = weekDef.weekBasedYear(); if (count == 2) { - return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 0); + return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, + this.subsequentWidth); } else { return new NumberPrinterParser(field, count, 19, - (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1); + (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, + this.subsequentWidth); } case 'e': case 'c': @@ -4828,7 +4881,8 @@ default: throw new IllegalStateException("unreachable"); } - return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE); + return new NumberPrinterParser(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, + this.subsequentWidth); } @Override diff -r 691c320b44f7 -r 0d68b06248fb jdk/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java --- a/jdk/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java Wed Dec 21 07:49:36 2016 -0800 +++ b/jdk/test/java/time/tck/java/time/format/TCKLocalizedFieldParser.java Wed Dec 21 18:45:34 2016 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,9 +66,11 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.WeekFields; +import java.util.Locale; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -79,12 +81,17 @@ */ @Test public class TCKLocalizedFieldParser extends AbstractTestPrinterParser { - + public static final WeekFields WEEKDEF = WeekFields.of(Locale.US); + public static final TemporalField WEEK_BASED_YEAR = WEEKDEF.weekBasedYear(); + public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = WEEKDEF.weekOfWeekBasedYear(); + public static final TemporalField DAY_OF_WEEK = WEEKDEF.dayOfWeek(); //----------------------------------------------------------------------- @DataProvider(name="FieldPatterns") Object[][] provider_fieldPatterns() { return new Object[][] { - {"e", "6", 0, 1, 6}, + {"e", "6", 0, 1, 6}, + {"ee", "06", 0, 2, 6}, + {"c", "6", 0, 1 , 6}, {"W", "3", 0, 1, 3}, {"w", "29", 0, 2, 29}, {"ww", "29", 0, 2, 29}, @@ -99,6 +106,7 @@ WeekFields weekDef = WeekFields.of(locale); TemporalField field = null; switch(pattern.charAt(0)) { + case 'c' : case 'e' : field = weekDef.dayOfWeek(); break; @@ -176,9 +184,9 @@ {"Y-w-e", "2008-01-1", 0, 9, LocalDate.of(2007, 12, 30)}, {"Y-w-e", "2008-52-1", 0, 9, LocalDate.of(2008, 12, 21)}, {"Y-w-e", "2008-52-7", 0, 9, LocalDate.of(2008, 12, 27)}, - {"Y-w-e", "2009-01-01", 0, 10, LocalDate.of(2008, 12, 28)}, - {"Y-w-e", "2009-01-04", 0, 10, LocalDate.of(2008, 12, 31)}, - {"Y-w-e", "2009-01-05", 0, 10, LocalDate.of(2009, 1, 1)}, + {"Y-w-e", "2009-01-1", 0, 9, LocalDate.of(2008, 12, 28)}, + {"Y-w-e", "2009-01-4", 0, 9, LocalDate.of(2008, 12, 31)}, + {"Y-w-e", "2009-01-5", 0, 9, LocalDate.of(2009, 1, 1)}, }; } @@ -202,4 +210,77 @@ } } + //----------------------------------------------------------------------- + @DataProvider(name = "adjacentValuePatterns1") + Object[][] provider_adjacentValuePatterns1() { + return new Object[][] { + {"YYww", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, "1612", 2016, 12}, + {"YYYYww", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, "201612", 2016, 12}, + }; + } + + @Test(dataProvider = "adjacentValuePatterns1") + public void test_adjacentValuePatterns1(String pattern, TemporalField field1, TemporalField field2, + String text, int expected1, int expected2) { + DateTimeFormatter df = new DateTimeFormatterBuilder() + .appendPattern(pattern).toFormatter(Locale.US); + ParsePosition ppos = new ParsePosition(0); + TemporalAccessor parsed = df.parseUnresolved(text, ppos); + assertEquals(parsed.get(field1), expected1); + assertEquals(parsed.get(field2), expected2); + } + + @DataProvider(name = "adjacentValuePatterns2") + Object[][] provider_adjacentValuePatterns2() { + return new Object[][] { + {"YYYYwwc", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK, + "2016121", 2016, 12, 1}, + {"YYYYwwee", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK, + "20161201", 2016, 12, 1}, + {"YYYYwwe", WEEK_BASED_YEAR, WEEK_OF_WEEK_BASED_YEAR, DAY_OF_WEEK, + "2016121", 2016, 12, 1}, + }; + } + + @Test(dataProvider = "adjacentValuePatterns2") + public void test_adjacentValuePatterns2(String pattern, TemporalField field1, TemporalField field2, + TemporalField field3, String text, int expected1, int expected2, int expected3) { + DateTimeFormatter df = new DateTimeFormatterBuilder() + .appendPattern(pattern).toFormatter(Locale.US); + ParsePosition ppos = new ParsePosition(0); + TemporalAccessor parsed = df.parseUnresolved(text, ppos); + assertEquals(parsed.get(field1), expected1); + assertEquals(parsed.get(field2), expected2); + assertEquals(parsed.get(field3), expected3); + } + + @Test + public void test_adjacentValuePatterns3() { + String pattern = "yyyyMMddwwc"; + String text = "20120720296"; + DateTimeFormatter df = new DateTimeFormatterBuilder() + .appendPattern(pattern).toFormatter(Locale.US); + ParsePosition ppos = new ParsePosition(0); + TemporalAccessor parsed = df.parseUnresolved(text, ppos); + assertEquals(parsed.get(DAY_OF_WEEK), 6); + assertEquals(parsed.get(WEEK_OF_WEEK_BASED_YEAR), 29); + LocalDate result = LocalDate.parse(text, df); + LocalDate expectedValue = LocalDate.of(2012, 07, 20); + assertEquals(result, expectedValue, "LocalDate incorrect for " + pattern); + } + + @DataProvider(name = "invalidPatterns") + Object[][] provider_invalidPatterns() { + return new Object[][] { + {"W", "01"}, + {"c", "01"}, + {"e", "01"}, + {"yyyyMMddwwc", "201207202906"}, // 1 extra digit in the input + }; + } + + @Test(dataProvider = "invalidPatterns", expectedExceptions = DateTimeParseException.class) + public void test_invalidPatterns(String pattern, String value) { + DateTimeFormatter.ofPattern(pattern).parse(value); + } }