jdk/src/share/classes/java/time/temporal/WeekFields.java
changeset 19030 32f129cb6351
parent 17474 8c100beabcc0
child 20793 5acd4b8d70a1
--- a/jdk/src/share/classes/java/time/temporal/WeekFields.java	Thu Jul 18 10:49:08 2013 -0700
+++ b/jdk/src/share/classes/java/time/temporal/WeekFields.java	Thu Jul 18 11:02:02 2013 -0700
@@ -75,7 +75,9 @@
 
 import java.io.InvalidObjectException;
 import java.io.Serializable;
+import java.time.DateTimeException;
 import java.time.DayOfWeek;
+import java.time.ZoneId;
 import java.time.chrono.ChronoLocalDate;
 import java.time.chrono.Chronology;
 import java.time.format.ResolverStyle;
@@ -395,6 +397,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 +428,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 +474,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-year 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 +523,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 +565,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
      */
@@ -615,9 +701,9 @@
          * @param dow the day of the week
          * @return a ChronoLocalDate for the requested year, week of year, and day of week
          */
-        private ChronoLocalDate<?> ofWeekBasedYear(Chronology chrono,
+        private ChronoLocalDate ofWeekBasedYear(Chronology chrono,
                 int yowby, int wowby, int dow) {
-            ChronoLocalDate<?> date = chrono.date(yowby, 1, 1);
+            ChronoLocalDate date = chrono.date(yowby, 1, 1);
             int ldow = localizedDayOfWeek(date);
             int offset = startOfWeekOffset(1, ldow);
 
@@ -671,6 +757,11 @@
             return Math.floorMod(isoDow - sow, 7) + 1;
         }
 
+        private int localizedDayOfWeek(int isoDow) {
+            int sow = weekDef.getFirstDayOfWeek().getValue();
+            return Math.floorMod(isoDow - sow, 7) + 1;
+        }
+
         private long localizedWeekOfMonth(TemporalAccessor temporal) {
             int dow = localizedDayOfWeek(temporal);
             int dom = temporal.get(DAY_OF_MONTH);
@@ -800,75 +891,121 @@
         }
 
         @Override
-        public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value, ResolverStyle resolverStyle) {
-            int newValue = range.checkValidIntValue(value, this);
-            int sow = weekDef.getFirstDayOfWeek().getValue();
+        public ChronoLocalDate resolve(
+                Map<TemporalField, Long> fieldValues, Chronology chronology, ZoneId zone, ResolverStyle resolverStyle) {
+            final long value = fieldValues.get(this);
+            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);
-            }
-            if (temporal.isSupported(DAY_OF_WEEK) == false) {
+                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;
+                fieldValues.remove(this);
+                fieldValues.put(DAY_OF_WEEK, isoDow);
                 return null;
             }
-            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;
+
+            // can only build date if ISO day-of-week is present
+            if (fieldValues.containsKey(DAY_OF_WEEK) == false) {
+                return null;
+            }
+            int isoDow = DAY_OF_WEEK.checkValidIntValue(fieldValues.get(DAY_OF_WEEK));
+            int dow = localizedDayOfWeek(isoDow);
+
+            // build date
+            if (fieldValues.containsKey(YEAR)) {
+                int year = YEAR.checkValidIntValue(fieldValues.get(YEAR));  // validate
+                if (rangeUnit == MONTHS && fieldValues.containsKey(MONTH_OF_YEAR)) {  // week-of-month
+                    long month = fieldValues.get(MONTH_OF_YEAR);  // not validated yet
+                    return resolveWoM(fieldValues, chronology, 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);
-
-                    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;
+                if (rangeUnit == YEARS) {  // week-of-year
+                    return resolveWoY(fieldValues, chronology, year, newValue, dow, resolverStyle);
                 }
+            } else if ((rangeUnit == WEEK_BASED_YEARS || rangeUnit == FOREVER) &&
+                    fieldValues.containsKey(weekDef.weekBasedYear) &&
+                    fieldValues.containsKey(weekDef.weekOfWeekBasedYear)) { // week-of-week-based-year and year-of-week-based-year
+                return resolveWBY(fieldValues, chronology, dow, resolverStyle);
             }
             return null;
         }
 
-        //-----------------------------------------------------------------------
-        @Override
-        public String getName() {
-            return name;
+        private ChronoLocalDate resolveWoM(
+                Map<TemporalField, Long> fieldValues, 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");
+                }
+            }
+            fieldValues.remove(this);
+            fieldValues.remove(YEAR);
+            fieldValues.remove(MONTH_OF_YEAR);
+            fieldValues.remove(DAY_OF_WEEK);
+            return date;
         }
 
+        private ChronoLocalDate resolveWoY(
+                Map<TemporalField, Long> fieldValues, 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");
+                }
+            }
+            fieldValues.remove(this);
+            fieldValues.remove(YEAR);
+            fieldValues.remove(DAY_OF_WEEK);
+            return date;
+        }
+
+        private ChronoLocalDate resolveWBY(
+                Map<TemporalField, Long> fieldValues, Chronology chrono, int localDow, ResolverStyle resolverStyle) {
+            int yowby = weekDef.weekBasedYear.range().checkValidIntValue(
+                    fieldValues.get(weekDef.weekBasedYear), weekDef.weekBasedYear);
+            ChronoLocalDate date;
+            if (resolverStyle == ResolverStyle.LENIENT) {
+                date = ofWeekBasedYear(chrono, yowby, 1, localDow);
+                long wowby = fieldValues.get(weekDef.weekOfWeekBasedYear);
+                long weeks = Math.subtractExact(wowby, 1);
+                date = date.plus(weeks, WEEKS);
+            } else {
+                int wowby = weekDef.weekOfWeekBasedYear.range().checkValidIntValue(
+                        fieldValues.get(weekDef.weekOfWeekBasedYear), 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");
+                }
+            }
+            fieldValues.remove(this);
+            fieldValues.remove(weekDef.weekBasedYear);
+            fieldValues.remove(weekDef.weekOfWeekBasedYear);
+            fieldValues.remove(DAY_OF_WEEK);
+            return date;
+        }
+
+        //-----------------------------------------------------------------------
         @Override
         public String getDisplayName(Locale locale) {
             Objects.requireNonNull(locale, "locale");
@@ -876,9 +1013,9 @@
                 LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
                         .getLocaleResources(locale);
                 ResourceBundle rb = lr.getJavaTimeFormatData();
-                return rb.containsKey("field.week") ? rb.getString("field.week") : getName();
+                return rb.containsKey("field.week") ? rb.getString("field.week") : name;
             }
-            return getName();
+            return name;
         }
 
         @Override
@@ -897,6 +1034,11 @@
         }
 
         @Override
+        public boolean isTimeBased() {
+            return false;
+        }
+
+        @Override
         public ValueRange range() {
             return range;
         }
@@ -988,7 +1130,7 @@
         //-----------------------------------------------------------------------
         @Override
         public String toString() {
-            return getName() + "[" + weekDef.toString() + "]";
+            return name + "[" + weekDef.toString() + "]";
         }
     }
 }