--- 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() + "]";
}
}
}