src/share/classes/java/time/temporal/IsoFields.java
Print this page
@@ -67,17 +67,23 @@
import static java.time.temporal.ChronoUnit.FOREVER;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.WEEKS;
import static java.time.temporal.ChronoUnit.YEARS;
-import java.time.DateTimeException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
+import java.time.format.ResolverStyle;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
+import java.util.ResourceBundle;
+
+import sun.util.locale.provider.LocaleProviderAdapter;
+import sun.util.locale.provider.LocaleResources;
/**
* Fields and units specific to the ISO-8601 calendar system,
* including quarter-of-year and week-based-year.
* <p>
@@ -160,37 +166,90 @@
* <p>
* When setting this field, the value is allowed to be partially lenient, taking any
* value from 1 to 92. If the quarter has less than 92 days, then day 92, and
* potentially day 91, is in the following quarter.
* <p>
+ * In the resolving phase of parsing, a date can be created from a year,
+ * quarter-of-year and day-of-quarter.
+ * <p>
+ * In {@linkplain ResolverStyle#STRICT strict mode}, all three fields are
+ * validated against their range of valid values. The day-of-quarter field
+ * is validated from 1 to 90, 91 or 92 depending on the year and quarter.
+ * <p>
+ * In {@linkplain ResolverStyle#SMART smart mode}, all three fields are
+ * validated against their range of valid values. The day-of-quarter field is
+ * validated between 1 and 92, ignoring the actual range based on the year and quarter.
+ * If the day-of-quarter exceeds the actual range by one day, then the resulting date
+ * is one day later. If the day-of-quarter exceeds the actual range by two days,
+ * then the resulting date is two days later.
+ * <p>
+ * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the year is 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 of January
+ * in the requested year. Then take the quarter-of-year, subtract one, and add the
+ * amount in quarters to the date. Finally, take the day-of-quarter, subtract one,
+ * and add the amount in days to the date.
+ * <p>
* This unit is an immutable and thread-safe singleton.
*/
public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
/**
* The field that represents the quarter-of-year.
* <p>
* This field allows the quarter-of-year value to be queried and set.
* The quarter-of-year has values from 1 to 4.
* <p>
- * The day-of-quarter can only be calculated if the month-of-year is available.
+ * The quarter-of-year can only be calculated if the month-of-year is available.
+ * <p>
+ * In the resolving phase of parsing, a date can be created from a year,
+ * quarter-of-year and day-of-quarter.
+ * See {@link #DAY_OF_QUARTER} for details.
* <p>
* This unit is an immutable and thread-safe singleton.
*/
public static final TemporalField QUARTER_OF_YEAR = Field.QUARTER_OF_YEAR;
/**
* The field that represents the week-of-week-based-year.
* <p>
* This field allows the week of the week-based-year value to be queried and set.
+ * The week-of-week-based-year has values from 1 to 52, or 53 if the
+ * week-based-year has 53 weeks.
+ * <p>
+ * In the resolving phase of parsing, a date can be created from a
+ * week-based-year, week-of-week-based-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-week-based-year
+ * field is validated from 1 to 52 or 53 depending on the week-based-year.
+ * <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 between 1 and 53, ignoring the week-based-year.
+ * If the week-of-week-based-year is 53, but the week-based-year only has
+ * 52 weeks, then the resulting date is in week 1 of the following week-based-year.
+ * <p>
+ * In {@linkplain ResolverStyle#LENIENT lenient mode}, only the week-based-year
+ * is validated against the range of valid values. If the day-of-week is outside
+ * the range 1 to 7, then the resulting date is adjusted by a suitable number of
+ * weeks to reduce the day-of-week to the range 1 to 7. If the week-of-week-based-year
+ * value is outside the range 1 to 52, then any excess weeks are added or subtracted
+ * from the resulting date.
* <p>
* This unit is an immutable and thread-safe singleton.
*/
public static final TemporalField WEEK_OF_WEEK_BASED_YEAR = Field.WEEK_OF_WEEK_BASED_YEAR;
/**
* The field that represents the week-based-year.
* <p>
* This field allows the week-based-year value to be queried and set.
* <p>
+ * The field has a range that matches {@link LocalDate#MAX} and {@link LocalDate#MIN}.
+ * <p>
+ * In the resolving phase of parsing, a date can be created from a
+ * week-based-year, week-of-week-based-year and day-of-week.
+ * See {@link #WEEK_OF_WEEK_BASED_YEAR} for details.
+ * <p>
* This unit is an immutable and thread-safe singleton.
*/
public static final TemporalField WEEK_BASED_YEAR = Field.WEEK_BASED_YEAR;
/**
* The unit that represents week-based-years for the purpose of addition and subtraction.
@@ -251,11 +310,11 @@
temporal.isSupported(YEAR) && isIso(temporal);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: DayOfQuarter");
+ throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
}
long qoy = temporal.getLong(QUARTER_OF_YEAR);
if (qoy == 1) {
long year = temporal.getLong(YEAR);
return (IsoChronology.INSTANCE.isLeapYear(year) ? ValueRange.of(1, 91) : ValueRange.of(1, 90));
@@ -267,11 +326,11 @@
return range();
}
@Override
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: DayOfQuarter");
+ throw new UnsupportedTemporalTypeException("Unsupported field: DayOfQuarter");
}
int doy = temporal.get(DAY_OF_YEAR);
int moy = temporal.get(MONTH_OF_YEAR);
long year = temporal.getLong(YEAR);
return doy - QUARTER_DAYS[((moy - 1) / 3) + (IsoChronology.INSTANCE.isLeapYear(year) ? 4 : 0)];
@@ -283,20 +342,33 @@
long curValue = getFrom(temporal);
range().checkValidValue(newValue, this); // leniently check from 1 to 92 TODO: check
return (R) temporal.with(DAY_OF_YEAR, temporal.getLong(DAY_OF_YEAR) + (newValue - curValue));
}
@Override
- public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) {
- if ((temporal.isSupported(YEAR) && temporal.isSupported(DAY_OF_QUARTER)) == false) {
+ public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long doq, ResolverStyle resolverStyle) {
+ if ((temporal.isSupported(YEAR) && temporal.isSupported(QUARTER_OF_YEAR)) == false) {
return null;
}
- int y = temporal.get(YEAR);
- int qoy = temporal.get(QUARTER_OF_YEAR);
- range().checkValidValue(value, this); // leniently check from 1 to 92 TODO: check
- LocalDate date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1).plusDays(value - 1);
+ int y = temporal.get(YEAR); // validated
+ LocalDate date;
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long qoy = temporal.getLong(QUARTER_OF_YEAR); // unvalidated
+ date = LocalDate.of(y, 1, 1).plusMonths(Math.multiplyExact(Math.subtractExact(qoy, 1), 3));
+ } else {
+ int qoy = temporal.get(QUARTER_OF_YEAR); // validated
+ date = LocalDate.of(y, ((qoy - 1) * 3) + 1, 1);
+ if (doq < 1 || doq > 90) {
+ if (resolverStyle == ResolverStyle.STRICT) {
+ rangeRefinedBy(date).checkValidValue(doq, this); // only allow exact range
+ } else { // SMART
+ range().checkValidValue(doq, this); // allow 1-92 rolling into next quarter
+ }
+ }
+ }
+ long epochDay = Math.addExact(date.toEpochDay(), Math.subtractExact(doq, 1));
Map<TemporalField, Long> result = new HashMap<>(4, 1.0f);
- result.put(EPOCH_DAY, date.toEpochDay());
+ result.put(EPOCH_DAY, epochDay);
result.put(YEAR, null);
result.put(QUARTER_OF_YEAR, null);
return result;
}
},
@@ -322,11 +394,11 @@
return temporal.isSupported(MONTH_OF_YEAR) && isIso(temporal);
}
@Override
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: QuarterOfYear");
+ throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");
}
long moy = temporal.getLong(MONTH_OF_YEAR);
return ((moy + 2) / 3);
}
@SuppressWarnings("unchecked")
@@ -341,10 +413,20 @@
WEEK_OF_WEEK_BASED_YEAR {
@Override
public String getName() {
return "WeekOfWeekBasedYear";
}
+
+ @Override
+ public String getDisplayName(Locale locale) {
+ Objects.requireNonNull(locale, "locale");
+ LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
+ .getLocaleResources(locale);
+ ResourceBundle rb = lr.getJavaTimeFormatData();
+ return rb.containsKey("field.week") ? rb.getString("field.week") : getName();
+ }
+
@Override
public TemporalUnit getBaseUnit() {
return WEEKS;
}
@Override
@@ -360,18 +442,18 @@
return temporal.isSupported(EPOCH_DAY) && isIso(temporal);
}
@Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: WeekOfWeekBasedYear");
+ throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");
}
return getWeekRange(LocalDate.from(temporal));
}
@Override
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: WeekOfWeekBasedYear");
+ throw new UnsupportedTemporalTypeException("Unsupported field: WeekOfWeekBasedYear");
}
return getWeek(LocalDate.from(temporal));
}
@SuppressWarnings("unchecked")
@Override
@@ -379,18 +461,37 @@
// calls getFrom() to check if supported
range().checkValidValue(newValue, this); // lenient range
return (R) temporal.plus(Math.subtractExact(newValue, getFrom(temporal)), WEEKS);
}
@Override
- public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long value) {
+ public Map<TemporalField, Long> resolve(TemporalAccessor temporal, long wowby, ResolverStyle resolverStyle) {
if ((temporal.isSupported(WEEK_BASED_YEAR) && temporal.isSupported(DAY_OF_WEEK)) == false) {
return null;
}
- int wby = temporal.get(WEEK_BASED_YEAR);
- int dow = temporal.get(DAY_OF_WEEK);
- range().checkValidValue(value, this); // lenient range
- LocalDate date = LocalDate.of(wby, 1, 4).plusWeeks(value - 1).with(DAY_OF_WEEK, dow);
+ int wby = temporal.get(WEEK_BASED_YEAR); // validated
+ LocalDate date = LocalDate.of(wby, 1, 4);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long dow = temporal.getLong(DAY_OF_WEEK); // unvalidated
+ if (dow > 7) {
+ date = date.plusWeeks((dow - 1) / 7);
+ dow = ((dow - 1) % 7) + 1;
+ } else if (dow < 1) {
+ date = date.plusWeeks(Math.subtractExact(dow, 7) / 7);
+ dow = ((dow + 6) % 7) + 1;
+ }
+ date = date.plusWeeks(Math.subtractExact(wowby, 1)).with(DAY_OF_WEEK, dow);
+ } else {
+ int dow = temporal.get(DAY_OF_WEEK); // validated
+ if (wowby < 1 || wowby > 52) {
+ if (resolverStyle == ResolverStyle.STRICT) {
+ getWeekRange(date).checkValidValue(wowby, this); // only allow exact range
+ } else { // SMART
+ range().checkValidValue(wowby, this); // allow 1-53 rolling into next year
+ }
+ }
+ date = date.plusWeeks(wowby - 1).with(DAY_OF_WEEK, dow);
+ }
Map<TemporalField, Long> result = new HashMap<>(2, 1.0f);
result.put(EPOCH_DAY, date.toEpochDay());
result.put(WEEK_BASED_YEAR, null);
result.put(DAY_OF_WEEK, null);
return result;
@@ -418,29 +519,34 @@
return temporal.isSupported(EPOCH_DAY) && isIso(temporal);
}
@Override
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: WeekBasedYear");
+ throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");
}
return getWeekBasedYear(LocalDate.from(temporal));
}
@SuppressWarnings("unchecked")
@Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
if (isSupportedBy(temporal) == false) {
- throw new DateTimeException("Unsupported field: WeekBasedYear");
+ throw new UnsupportedTemporalTypeException("Unsupported field: WeekBasedYear");
}
int newVal = range().checkValidIntValue(newValue, WEEK_BASED_YEAR); // strict check
LocalDate date = LocalDate.from(temporal);
int week = getWeek(date);
date = date.withDayOfYear(180).withYear(newVal).with(WEEK_OF_WEEK_BASED_YEAR, week);
return (R) date.with(date);
}
};
@Override
+ public boolean isDateBased() {
+ return true;
+ }
+
+ @Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
return range();
}
@Override