ThreeTen / threeten

This project was the home of code used to develop a modern date and time library for JDK8. Development has moved to OpenJDK and a separate backport project, threetenbp.
http://threeten.github.io/
191 stars 37 forks source link

Lenient parsing of WeekFields #317

Closed jodastephen closed 11 years ago

jodastephen commented 11 years ago

Allow lenient/strict/smart parsing of WeekFields. Breakout from #282

jodastephen commented 11 years ago
diff -r 6bbcad16b876 src/share/classes/java/time/temporal/WeekFields.java
--- a/src/share/classes/java/time/temporal/WeekFields.java  Sun May 26 00:05:48 2013 +0100
+++ b/src/share/classes/java/time/temporal/WeekFields.java  Thu May 30 01:02:29 2013 +0100
@@ -75,6 +75,7 @@

 import java.io.InvalidObjectException;
 import java.io.Serializable;
+import java.time.DateTimeException;
 import java.time.DayOfWeek;
 import java.time.chrono.ChronoLocalDate;
 import java.time.chrono.Chronology;
@@ -395,6 +396,11 @@
      * <p>
      * For example, if the first day-of-week is Sunday, then that will have the
      * value 1, with other days ranging from Monday as 2 to Saturday as 7.
+     * <p>
+     * In the resolving phase of parsing, a localized day-of-week will be converted
+     * to a standardized {@code ChronoField} day-of-week.
+     * The day-of-week must be in the valid range 1 to 7.
+     * Other fields in this class build dates using the standardized day-of-week.
      *
      * @return a field providing access to the day-of-week with localized numbering, not null
      */
@@ -421,6 +427,26 @@
      * - if the 5th day of the month is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br>
      * <p>
      * This field can be used with any calendar system.
+     * <p>
+     * In the resolving phase of parsing, a date can be created from a year,
+     * week-of-month, month-of-year and day-of-week.
+     * <p>
+     * In {@linkplain ResolverStyle#STRICT strict mode}, all four fields are
+     * validated against their range of valid values. The week-of-month field
+     * is validated to ensure that the resulting month is the month requested.
+     * <p>
+     * In {@linkplain ResolverStyle#SMART smart mode}, all four fields are
+     * validated against their range of valid values. The week-of-month field
+     * is validated from 0 to 6, meaning that the resulting date can be in a
+     * different month to that specified.
+     * <p>
+     * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
+     * are validated against the range of valid values. The resulting date is calculated
+     * equivalent to the following four stage approach.
+     * First, create a date on the first day of the first week of January in the requested year.
+     * Then take the month-of-year, subtract one, and add the amount in months to the date.
+     * Then take the week-of-month, subtract one, and add the amount in weeks to the date.
+     * Finally, adjust to the correct day-of-week within the localized week.
      *
      * @return a field providing access to the week-of-month, not null
      */
@@ -447,6 +473,25 @@
      * - if the 5th day of the year is a Monday, week two starts on the 5th and the 1st to 4th is in week one<br>
      * <p>
      * This field can be used with any calendar system.
+     * <p>
+     * In the resolving phase of parsing, a date can be created from a year,
+     * week-of-year and day-of-week.
+     * <p>
+     * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
+     * validated against their range of valid values. The week-of-year field
+     * is validated to ensure that the resulting year is the year requested.
+     * <p>
+     * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
+     * validated against their range of valid values. The week-of-month field
+     * is validated from 0 to 54, meaning that the resulting date can be in a
+     * different year to that specified.
+     * <p>
+     * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
+     * are validated against the range of valid values. The resulting date is calculated
+     * equivalent to the following three stage approach.
+     * First, create a date on the first day of the first week in the requested year.
+     * Then take the week-of-year, subtract one, and add the amount in weeks to the date.
+     * Finally, adjust to the correct day-of-week within the localized week.
      *
      * @return a field providing access to the week-of-year, not null
      */
@@ -477,6 +522,26 @@
      *   the 1st to 4th is in week one<br>
      * <p>
      * This field can be used with any calendar system.
+     * <p>
+     * In the resolving phase of parsing, a date can be created from a week-based-year,
+     * week-of-year and day-of-week.
+     * <p>
+     * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
+     * validated against their range of valid values. The week-of-year field
+     * is validated to ensure that the resulting week-based-year is the
+     * week-based-year requested.
+     * <p>
+     * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
+     * validated against their range of valid values. The week-of-week-based-year field
+     * is validated from 1 to 53, meaning that the resulting date can be in the
+     * following week-based-year to that specified.
+     * <p>
+     * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
+     * are validated against the range of valid values. The resulting date is calculated
+     * equivalent to the following three stage approach.
+     * First, create a date on the first day of the first week in the requested week-based-year.
+     * Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.
+     * Finally, adjust to the correct day-of-week within the localized week.
      *
      * @return a field providing access to the week-of-week-based-year, not null
      */
@@ -499,6 +564,26 @@
      * is in the last week of the previous year.
      * <p>
      * This field can be used with any calendar system.
+     * <p>
+     * In the resolving phase of parsing, a date can be created from a week-based-year,
+     * week-of-year and day-of-week.
+     * <p>
+     * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
+     * validated against their range of valid values. The week-of-year field
+     * is validated to ensure that the resulting week-based-year is the
+     * week-based-year requested.
+     * <p>
+     * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
+     * validated against their range of valid values. The week-of-week-based-year field
+     * is validated from 1 to 53, meaning that the resulting date can be in the
+     * following week-based-year to that specified.
+     * <p>
+     * In {@linkplain ResolverStyle#LENIENT lenient mode}, the year and day-of-week
+     * are validated against the range of valid values. The resulting date is calculated
+     * equivalent to the following three stage approach.
+     * First, create a date on the first day of the first week in the requested week-based-year.
+     * Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date.
+     * Finally, adjust to the correct day-of-week within the localized week.
      *
      * @return a field providing access to the week-based-year, not null
      */
@@ -801,66 +886,114 @@

         @Override
         public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value, ResolverStyle resolverStyle) {
-            int newValue = range.checkValidIntValue(value, this);
-            int sow = weekDef.getFirstDayOfWeek().getValue();
+            final int newValue = Math.toIntExact(value);  // broad limit makes overflow checking lighter
+            // first convert localized day-of-week to ISO day-of-week
+            // doing this first handles case where both ISO and localized were parsed and might mismatch
+            // day-of-week is always strict as two different day-of-week values makes lenient complex
             if (rangeUnit == WEEKS) {  // day-of-week
-                int isoDow = Math.floorMod((sow - 1) + (newValue - 1), 7) + 1;
-                return Collections.<TemporalField, Long>singletonMap(DAY_OF_WEEK, (long) isoDow);
+                final int checkedValue = range.checkValidIntValue(value, this);  // no leniency as too complex
+                final int startDow = weekDef.getFirstDayOfWeek().getValue();
+                long isoDow = Math.floorMod((startDow - 1) + (checkedValue - 1), 7) + 1;
+                return Collections.<TemporalField, Long>singletonMap(DAY_OF_WEEK, isoDow);
             }
+
+            // can only build date if ISO day-of-week is present
             if (temporal.isSupported(DAY_OF_WEEK) == false) {
                 return null;
             }
+            int dow = localizedDayOfWeek(temporal);
+
+            // build date
             Chronology chrono = Chronology.from(temporal);  // defaults to ISO
-            int dow = localizedDayOfWeek(temporal);
             if (temporal.isSupported(YEAR)) {
-                int year = temporal.get(YEAR);
-                if (rangeUnit == MONTHS) {  // week-of-month
-                    if (temporal.isSupported(MONTH_OF_YEAR) == false) {
-                        return null;
-                    }
-                    int month = temporal.get(ChronoField.MONTH_OF_YEAR);
-                @SuppressWarnings("rawtypes")
-                    ChronoLocalDate date = chrono.date(year, month, 1);
-                    int dateDow = localizedDayOfWeek(date);
-                    long weeks = newValue - localizedWeekOfMonth(date);
-                    int days = dow - dateDow;
-                    date = date.plus(weeks * 7 + days, DAYS);
-                    Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
-                    result.put(EPOCH_DAY, date.toEpochDay());
-                    result.put(YEAR, null);
-                    result.put(MONTH_OF_YEAR, null);
-                    result.put(DAY_OF_WEEK, null);
-                    return result;
-                } else if (rangeUnit == YEARS) {  // week-of-year
-                @SuppressWarnings("rawtypes")
-                    ChronoLocalDate date = chrono.date(year, 1, 1);
-                    int dateDow = localizedDayOfWeek(date);
-                    long weeks = newValue - localizedWeekOfYear(date);
-                    int days = dow - dateDow;
-                    date = date.plus(weeks * 7 + days, DAYS);
-                    Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
-                    result.put(EPOCH_DAY, date.toEpochDay());
-                    result.put(YEAR, null);
-                    result.put(DAY_OF_WEEK, null);
-                    return result;
+                int year = temporal.get(YEAR);  // validate
+                if (rangeUnit == MONTHS && temporal.isSupported(MONTH_OF_YEAR)) {  // week-of-month
+                    long month = temporal.getLong(MONTH_OF_YEAR);  // not validates yet
+                    return resolveWoM(chrono, year, month, newValue, dow, resolverStyle);
                 }
-            } else if (rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) {
-                if (temporal.isSupported(weekDef.weekBasedYear) &&
-                    temporal.isSupported(weekDef.weekOfWeekBasedYear)) {
-                    // week-of-week-based-year and year-of-week-based-year
-                    int yowby = temporal.get(weekDef.weekBasedYear);
-                    int wowby = temporal.get(weekDef.weekOfWeekBasedYear);
-                    ChronoLocalDate<?> date = ofWeekBasedYear(Chronology.from(temporal), yowby, wowby, dow);
+                if (rangeUnit == YEARS) {  // week-of-year
+                    return resolveWoY(chrono, year, newValue, dow, resolverStyle);
+                }
+            } else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) &&
+                    temporal.isSupported(weekDef.weekBasedYear) &&
+                    temporal.isSupported(weekDef.weekOfWeekBasedYear)) { // week-of-week-based-year and year-of-week-based-year
+                return resolveWBY(chrono, temporal, dow, resolverStyle);
+            }
+            return null;
+        }

-                    Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
-                    result.put(EPOCH_DAY, date.toEpochDay());
-                    result.put(DAY_OF_WEEK, null);
-                    result.put(weekDef.weekOfWeekBasedYear, null);
-                    result.put(weekDef.weekBasedYear, null);
-                    return result;
+        private Map<TemporalField, Long> resolveWoM(
+                Chronology chrono, int year, long month, long wom, int localDow, ResolverStyle resolverStyle) {
+            ChronoLocalDate<?> date;
+            if (resolverStyle == ResolverStyle.LENIENT) {
+                date = chrono.date(year, 1, 1).plus(Math.subtractExact(month, 1), MONTHS);
+                long weeks = Math.subtractExact(wom, localizedWeekOfMonth(date));
+                int days = localDow - localizedDayOfWeek(date);  // safe from overflow
+                date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);
+            } else {
+                int monthValid = MONTH_OF_YEAR.checkValidIntValue(month);  // validate
+                date = chrono.date(year, monthValid, 1);
+                int womInt = range.checkValidIntValue(wom, this);  // validate
+                int weeks = (int) (womInt - localizedWeekOfMonth(date));  // safe from overflow
+                int days = localDow - localizedDayOfWeek(date);  // safe from overflow
+                date = date.plus(weeks * 7 + days, DAYS);
+                if (resolverStyle == ResolverStyle.STRICT && date.getLong(MONTH_OF_YEAR) != month) {
+                    throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
                 }
             }
-            return null;
+            Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
+            result.put(YEAR, null);
+            result.put(MONTH_OF_YEAR, null);
+            result.put(DAY_OF_WEEK, null);
+            result.put(EPOCH_DAY, date.toEpochDay());
+            return result;
+        }
+
+        private Map<TemporalField, Long> resolveWoY(
+                Chronology chrono, int year, long woy, int localDow, ResolverStyle resolverStyle) {
+            ChronoLocalDate<?> date = chrono.date(year, 1, 1);
+            if (resolverStyle == ResolverStyle.LENIENT) {
+                long weeks = Math.subtractExact(woy, localizedWeekOfYear(date));
+                int days = localDow - localizedDayOfWeek(date);  // safe from overflow
+                date = date.plus(Math.addExact(Math.multiplyExact(weeks, 7), days), DAYS);
+            } else {
+                int womInt = range.checkValidIntValue(woy, this);  // validate
+                int weeks = (int) (womInt - localizedWeekOfYear(date));  // safe from overflow
+                int days = localDow - localizedDayOfWeek(date);  // safe from overflow
+                date = date.plus(weeks * 7 + days, DAYS);
+                if (resolverStyle == ResolverStyle.STRICT && date.getLong(YEAR) != year) {
+                    throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
+                }
+            }
+            Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
+            result.put(YEAR, null);
+            result.put(DAY_OF_WEEK, null);
+            result.put(EPOCH_DAY, date.toEpochDay());
+            return result;
+        }
+
+        private Map<TemporalField, Long> resolveWBY(
+                Chronology chrono, TemporalAccessor temporal, int localDow, ResolverStyle resolverStyle) {
+            int yowby = temporal.get(weekDef.weekBasedYear);
+            ChronoLocalDate<?> date;
+            if (resolverStyle == ResolverStyle.LENIENT) {
+                date = ofWeekBasedYear(chrono, yowby, 1, localDow);
+                long wowby = temporal.getLong(weekDef.weekOfWeekBasedYear);
+                long weeks = Math.subtractExact(wowby, 1);
+                date = date.plus(weeks, WEEKS);
+            } else {
+                int wowby = temporal.get(weekDef.weekOfWeekBasedYear);  // validate
+                date = ofWeekBasedYear(chrono, yowby, wowby, localDow);
+                if (resolverStyle == ResolverStyle.STRICT && localizedWeekBasedYear(date) != yowby) {
+                    throw new DateTimeException("Strict mode rejected resolved date as it is in a different week-based-year");
+                }
+            }
+            Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
+            result.put(weekDef.weekBasedYear, null);
+            result.put(weekDef.weekOfWeekBasedYear, null);
+            result.put(DAY_OF_WEEK, null);
+            result.put(EPOCH_DAY, date.toEpochDay());
+            return result;
         }

         //-----------------------------------------------------------------------
diff -r 6bbcad16b876 test/java/time/tck/java/time/temporal/TCKWeekFields.java
--- a/test/java/time/tck/java/time/temporal/TCKWeekFields.java  Sun May 26 00:05:48 2013 +0100
+++ b/test/java/time/tck/java/time/temporal/TCKWeekFields.java  Thu May 30 01:02:29 2013 +0100
@@ -56,6 +56,10 @@
  */
 package tck.java.time.temporal;

+import static java.time.format.ResolverStyle.LENIENT;
+import static java.time.format.ResolverStyle.SMART;
+import static java.time.format.ResolverStyle.STRICT;
+import static java.time.temporal.ChronoField.AMPM_OF_DAY;
 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
 import static java.time.temporal.ChronoField.DAY_OF_YEAR;
@@ -64,14 +68,20 @@

 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertSame;
+import static org.testng.Assert.fail;

 import java.io.IOException;
+import java.time.DateTimeException;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
 import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalQuery;
 import java.time.temporal.ValueRange;
 import java.time.temporal.WeekFields;

@@ -376,14 +386,13 @@
         TemporalField womField = week.weekOfMonth();

         for (int i = 1; i <= 60; i++) {
-            // Test that with dayOfWeek and Week of month it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(YEAR).appendLiteral('-')
-                    .appendValue(MONTH_OF_YEAR).appendLiteral('-')
-                    .appendValue(womField).appendLiteral('-')
-                    .appendValue(DAY_OF_WEEK).toFormatter();
-            String str = date.getYear() + "-" + date.getMonthValue() + "-" +
-                    date.get(womField) + "-" + date.get(DAY_OF_WEEK);
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                    .appendValue(womField).appendLiteral(':')
+                    .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(SMART);
+            String str = date.getYear() + ":" + date.getMonthValue() + ":" +
+                    date.get(womField) + ":" + date.get(DAY_OF_WEEK);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " ::" + str + "::" + i);

@@ -392,6 +401,52 @@
     }

     @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWom_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 15);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField womField = week.weekOfMonth();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                    .appendValue(womField).appendLiteral(':')
+                    .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(LENIENT);
+            int wom = date.get(womField);
+            int dow = date.get(DAY_OF_WEEK);
+            for (int j = wom - 10; j < wom + 10; j++) {
+                String str = date.getYear() + ":" + date.getMonthValue() + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - wom), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
+    @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWom_strict(DayOfWeek firstDayOfWeek, int minDays) {
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField womField = week.weekOfMonth();
+        DateTimeFormatter f = new DateTimeFormatterBuilder()
+                .appendValue(YEAR).appendLiteral(':')
+                .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                .appendValue(womField).appendLiteral(':')
+                .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(STRICT);
+        String str = "2012:1:0:1";
+        try {
+            LocalDate date = LocalDate.parse(str, f);
+            assertEquals(date.getYear(), 2012);
+            assertEquals(date.getMonthValue(), 1);
+            assertEquals(date.get(womField), 0);
+            assertEquals(date.get(DAY_OF_WEEK), 1);
+        } catch (DateTimeException ex) {
+            // expected
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(dataProvider="weekFields")
     public void test_parse_resolve_localizedWomDow(DayOfWeek firstDayOfWeek, int minDays) {
         LocalDate date = LocalDate.of(2012, 12, 15);
         WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
@@ -399,14 +454,13 @@
         TemporalField womField = week.weekOfMonth();

         for (int i = 1; i <= 15; i++) {
-            // Test that with dayOfWeek and Week of month it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(YEAR).appendLiteral('-')
-                    .appendValue(MONTH_OF_YEAR).appendLiteral('-')
-                    .appendValue(womField).appendLiteral('-')
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                    .appendValue(womField).appendLiteral(':')
                     .appendValue(dowField).toFormatter();
-            String str = date.getYear() + "-" + date.getMonthValue() + "-" +
-                    date.get(womField) + "-" + date.get(dowField);
+            String str = date.getYear() + ":" + date.getMonthValue() + ":" +
+                    date.get(womField) + ":" + date.get(dowField);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " :: " + str + " " + i);

@@ -415,20 +469,44 @@
     }

     @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWomDow_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 15);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField dowField = week.dayOfWeek();
+        TemporalField womField = week.weekOfMonth();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                    .appendValue(womField).appendLiteral(':')
+                    .appendValue(dowField).toFormatter().withResolverStyle(LENIENT);
+            int wom = date.get(womField);
+            int dow = date.get(dowField);
+            for (int j = wom - 10; j < wom + 10; j++) {
+                String str = date.getYear() + ":" + date.getMonthValue() + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - wom), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(dataProvider="weekFields")
     public void test_parse_resolve_localizedWoy(DayOfWeek firstDayOfWeek, int minDays) {
         LocalDate date = LocalDate.of(2012, 12, 15);
         WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
         TemporalField woyField = week.weekOfYear();

         for (int i = 1; i <= 60; i++) {
-            // Test that with dayOfWeek and Week of month it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(YEAR).appendLiteral('-')
-                    .appendValue(MONTH_OF_YEAR).appendLiteral('-')
-                    .appendValue(woyField).appendLiteral('-')
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(woyField).appendLiteral(':')
                     .appendValue(DAY_OF_WEEK).toFormatter();
-            String str = date.getYear() + "-" + date.getMonthValue() + "-" +
-                    date.get(woyField) + "-" + date.get(DAY_OF_WEEK);
+            String str = date.getYear() + ":" +
+                    date.get(woyField) + ":" + date.get(DAY_OF_WEEK);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " :: " + str + " " + i);

@@ -437,6 +515,49 @@
     }

     @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoy_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 15);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField woyField = week.weekOfYear();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(woyField).appendLiteral(':')
+                    .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(LENIENT);
+            int woy = date.get(woyField);
+            int dow = date.get(DAY_OF_WEEK);
+            for (int j = woy - 60; j < woy + 60; j++) {
+                String str = date.getYear() + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - woy), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
+    @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoy_strict(DayOfWeek firstDayOfWeek, int minDays) {
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField woyField = week.weekOfYear();
+        DateTimeFormatter f = new DateTimeFormatterBuilder()
+                .appendValue(YEAR).appendLiteral(':')
+                .appendValue(woyField).appendLiteral(':')
+                .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(STRICT);
+        String str = "2012:0:1";
+        try {
+            LocalDate date = LocalDate.parse(str, f);
+            assertEquals(date.getYear(), 2012);
+            assertEquals(date.get(woyField), 0);
+            assertEquals(date.get(DAY_OF_WEEK), 1);
+        } catch (DateTimeException ex) {
+            // expected
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(dataProvider="weekFields")
     public void test_parse_resolve_localizedWoyDow(DayOfWeek firstDayOfWeek, int minDays) {
         LocalDate date = LocalDate.of(2012, 12, 15);
         WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
@@ -444,14 +565,13 @@
         TemporalField woyField = week.weekOfYear();

         for (int i = 1; i <= 60; i++) {
-            // Test that with dayOfWeek and Week of month it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(YEAR).appendLiteral('-')
-                    .appendValue(MONTH_OF_YEAR).appendLiteral('-')
-                    .appendValue(woyField).appendLiteral('-')
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(MONTH_OF_YEAR).appendLiteral(':')
+                    .appendValue(woyField).appendLiteral(':')
                     .appendValue(dowField).toFormatter();
-            String str = date.getYear() + "-" + date.getMonthValue() + "-" +
-                    date.get(woyField) + "-" + date.get(dowField);
+            String str = date.getYear() + ":" + date.getMonthValue() + ":" +
+                    date.get(woyField) + ":" + date.get(dowField);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " :: " + str + " " + i);

@@ -460,6 +580,31 @@
     }

     @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoyDow_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 15);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField dowField = week.dayOfWeek();
+        TemporalField woyField = week.weekOfYear();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(YEAR).appendLiteral(':')
+                    .appendValue(woyField).appendLiteral(':')
+                    .appendValue(dowField).toFormatter().withResolverStyle(LENIENT);
+            int woy = date.get(woyField);
+            int dow = date.get(dowField);
+            for (int j = woy - 60; j < woy + 60; j++) {
+                String str = date.getYear() + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - woy), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(dataProvider="weekFields")
     public void test_parse_resolve_localizedWoWBY(DayOfWeek firstDayOfWeek, int minDays) {
         LocalDate date = LocalDate.of(2012, 12, 31);
         WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
@@ -467,12 +612,11 @@
         TemporalField yowbyField = week.weekBasedYear();

         for (int i = 1; i <= 60; i++) {
-            // Test that with dayOfWeek, week of year and year of week-based-year it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(yowbyField).appendLiteral('-')
-                    .appendValue(wowbyField).appendLiteral('-')
+                    .appendValue(yowbyField).appendLiteral(':')
+                    .appendValue(wowbyField).appendLiteral(':')
                     .appendValue(DAY_OF_WEEK).toFormatter();
-            String str = date.get(yowbyField) + "-" + date.get(wowbyField) + "-" +
+            String str = date.get(yowbyField) + ":" + date.get(wowbyField) + ":" +
                     date.get(DAY_OF_WEEK);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " :: " + str + " " + i);
@@ -482,6 +626,51 @@
     }

     @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoWBY_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 31);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField wowbyField = week.weekOfWeekBasedYear();
+        TemporalField yowbyField = week.weekBasedYear();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(yowbyField).appendLiteral(':')
+                    .appendValue(wowbyField).appendLiteral(':')
+                    .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(LENIENT);
+            int wowby = date.get(wowbyField);
+            int dow = date.get(DAY_OF_WEEK);
+            for (int j = wowby - 60; j < wowby + 60; j++) {
+                String str = date.get(yowbyField) + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - wowby), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
+    @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoWBY_strict(DayOfWeek firstDayOfWeek, int minDays) {
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField wowbyField = week.weekOfWeekBasedYear();
+        TemporalField yowbyField = week.weekBasedYear();
+        DateTimeFormatter f = new DateTimeFormatterBuilder()
+                .appendValue(yowbyField).appendLiteral(':')
+                .appendValue(wowbyField).appendLiteral(':')
+                .appendValue(DAY_OF_WEEK).toFormatter().withResolverStyle(STRICT);
+        String str = "2012:0:1";
+        try {
+            LocalDate date = LocalDate.parse(str, f);
+            assertEquals(date.get(yowbyField), 2012);
+            assertEquals(date.get(wowbyField), 0);
+            assertEquals(date.get(DAY_OF_WEEK), 1);
+        } catch (DateTimeException ex) {
+            // expected
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(dataProvider="weekFields")
     public void test_parse_resolve_localizedWoWBYDow(DayOfWeek firstDayOfWeek, int minDays) {
         LocalDate date = LocalDate.of(2012, 12, 31);
         WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
@@ -490,12 +679,11 @@
         TemporalField yowbyField = week.weekBasedYear();

         for (int i = 1; i <= 60; i++) {
-            // Test that with dayOfWeek, week of year and year of week-based-year it computes the date
             DateTimeFormatter f = new DateTimeFormatterBuilder()
-                    .appendValue(yowbyField).appendLiteral('-')
-                    .appendValue(wowbyField).appendLiteral('-')
+                    .appendValue(yowbyField).appendLiteral(':')
+                    .appendValue(wowbyField).appendLiteral(':')
                     .appendValue(dowField).toFormatter();
-            String str = date.get(yowbyField) + "-" + date.get(wowbyField) + "-" +
+            String str = date.get(yowbyField) + ":" + date.get(wowbyField) + ":" +
                     date.get(dowField);
             LocalDate parsed = LocalDate.parse(str, f);
             assertEquals(parsed, date, " :: " + str + " " + i);
@@ -504,6 +692,31 @@
         }
     }

+    @Test(dataProvider="weekFields")
+    public void test_parse_resolve_localizedWoWBYDow_lenient(DayOfWeek firstDayOfWeek, int minDays) {
+        LocalDate date = LocalDate.of(2012, 12, 31);
+        WeekFields week = WeekFields.of(firstDayOfWeek, minDays);
+        TemporalField dowField = week.dayOfWeek();
+        TemporalField wowbyField = week.weekOfWeekBasedYear();
+        TemporalField yowbyField = week.weekBasedYear();
+
+        for (int i = 1; i <= 60; i++) {
+            DateTimeFormatter f = new DateTimeFormatterBuilder()
+                    .appendValue(yowbyField).appendLiteral(':')
+                    .appendValue(wowbyField).appendLiteral(':')
+                    .appendValue(dowField).toFormatter().withResolverStyle(LENIENT);
+            int wowby = date.get(wowbyField);
+            int dow = date.get(dowField);
+            for (int j = wowby - 60; j < wowby + 60; j++) {
+                String str = date.get(yowbyField) + ":" + j + ":" + dow;
+                LocalDate parsed = LocalDate.parse(str, f);
+                assertEquals(parsed, date.plusWeeks(j - wowby), " ::" + str + ": :" + i + "::" + j);
+            }
+
+            date = date.plusDays(1);
+        }
+    }
+
     //-----------------------------------------------------------------------
     @Test(dataProvider="weekFields")
     public void test_serializable_singleton(DayOfWeek firstDayOfWeek, int minDays) throws IOException, ClassNotFoundException {
RogerRiggs commented 11 years ago

In WeekFields: @@ -447,6 +473,25 @@

" The week-of-month field

Should probably be week-of-year.

The rest looks fine.

jodastephen commented 11 years ago

Fixed by http://hg.openjdk.java.net/threeten/threeten/jdk/rev/cf33e461f819 and http://hg.openjdk.java.net/threeten/threeten/jdk/rev/73fac9acbaa1