Closed jodastephen closed 11 years ago
# HG changeset patch
# User scolebourne
# Date 1369956576 -3600
# Node ID bf5678a8051f8ff8af9078373e533a1456995a40
# Parent 9b12f3b38e140360093ddc7e0d7aea3f065fd1af
Lenient resolve for non-ISO calendars
See #318
diff -r 9b12f3b38e14 -r bf5678a8051f src/share/classes/java/time/chrono/Chronology.java
--- a/src/share/classes/java/time/chrono/Chronology.java Fri May 31 00:15:00 2013 +0100
+++ b/src/share/classes/java/time/chrono/Chronology.java Fri May 31 00:29:36 2013 +0100
@@ -74,6 +74,9 @@
import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoField.YEAR_OF_ERA;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MONTHS;
+import static java.time.temporal.ChronoUnit.WEEKS;
import static java.time.temporal.TemporalAdjuster.nextOrSame;
import java.io.DataInput;
@@ -88,14 +91,16 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
+import java.time.Month;
+import java.time.Year;
import java.time.ZoneId;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.ResolverStyle;
import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
-import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.UnsupportedTemporalTypeException;
@@ -929,11 +934,82 @@
* As such, {@code ChronoField} date fields are resolved here in the
* context of a specific chronology.
* <p>
+ * {@code ChronoField} instances are resolved by this method, which may
+ * be overridden in subclasses.
+ * <ul>
+ * <li>{@code EPOCH_DAY} - If present, this is converted to a date and
+ * all other date fields are then cross-checked against the date.
+ * <li>{@code PROLEPTIC_MONTH} - If present, then it is split into the
+ * {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart
+ * then the field is validated.
+ * <li>{@code YEAR_OF_ERA} and {@code ERA} - If both are present, then they
+ * are combined to form a {@code YEAR}. In lenient mode, the {@code YEAR_OF_ERA}
+ * range is not validated, in smart and strict mode it is. The {@code ERA} is
+ * validated for range in all three modes. If only the {@code YEAR_OF_ERA} is
+ * present, and the mode is smart or lenient, then the last available era
+ * is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is
+ * left untouched. If only the {@code ERA} is present, then it is left untouched.
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} -
+ * If all three are present, then they are combined to form a date.
+ * In all three modes, the {@code YEAR} is validated.
+ * If the mode is smart or strict, then the month and day are validated.
+ * If the mode is lenient, then the date is combined in a manner equivalent to
+ * creating a date on the first day of the first month in the requested year,
+ * then adding the difference in months, then the difference in days.
+ * If the mode is smart, and the day-of-month is greater than the maximum for
+ * the year-month, then the day-of-month is adjusted to the last day-of-month.
+ * If the mode is strict, then the three fields must form a valid date.
+ * <li>{@code YEAR} and {@code DAY_OF_YEAR} -
+ * If both are present, then they are combined to form a date.
+ * In all three modes, the {@code YEAR} is validated.
+ * If the mode is lenient, then the date is combined in a manner equivalent to
+ * creating a date on the first day of the requested year, then adding
+ * the difference in days.
+ * If the mode is smart or strict, then the two fields must form a valid date.
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
+ * {@code ALIGNED_DAY_OF_WEEK_IN_MONTH} -
+ * If all four are present, then they are combined to form a date.
+ * In all three modes, the {@code YEAR} is validated.
+ * If the mode is lenient, then the date is combined in a manner equivalent to
+ * creating a date on the first day of the first month in the requested year, then adding
+ * the difference in months, then the difference in weeks, then in days.
+ * If the mode is smart or strict, then the all four fields are validated to
+ * their outer ranges. The date is then combined in a manner equivalent to
+ * creating a date on the first day of the requested year and month, then adding
+ * the amount in weeks and days to reach their values. If the mode is strict,
+ * the date is additionally validated to check that the day and week adjustment
+ * did not change the month.
+ * <li>{@code YEAR}, {@code MONTH_OF_YEAR}, {@code ALIGNED_WEEK_OF_MONTH} and
+ * {@code DAY_OF_WEEK} - If all four are present, then they are combined to
+ * form a date. The approach is the same as described above for
+ * years, months and weeks in {@code ALIGNED_DAY_OF_WEEK_IN_MONTH}.
+ * The day-of-week is adjusted as the next or same matching day-of-week once
+ * the years, months and weeks have been handled.
+ * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code ALIGNED_DAY_OF_WEEK_IN_YEAR} -
+ * If all three are present, then they are combined to form a date.
+ * In all three modes, the {@code YEAR} is validated.
+ * If the mode is lenient, then the date is combined in a manner equivalent to
+ * creating a date on the first day of the requested year, then adding
+ * the difference in weeks, then in days.
+ * If the mode is smart or strict, then the all three fields are validated to
+ * their outer ranges. The date is then combined in a manner equivalent to
+ * creating a date on the first day of the requested year, then adding
+ * the amount in weeks and days to reach their values. If the mode is strict,
+ * the date is additionally validated to check that the day and week adjustment
+ * did not change the year.
+ * <li>{@code YEAR}, {@code ALIGNED_WEEK_OF_YEAR} and {@code DAY_OF_WEEK} -
+ * If all three are present, then they are combined to form a date.
+ * The approach is the same as described above for years and weeks in
+ * {@code ALIGNED_DAY_OF_WEEK_IN_YEAR}. The day-of-week is adjusted as the
+ * next or same matching day-of-week once the years and weeks have been handled.
+ * </ul>
+ * <p>
* The default implementation is suitable for most calendar systems.
* If {@link ChronoField#YEAR_OF_ERA} is found without an {@link ChronoField#ERA}
* then the last era in {@link #eras()} is used.
* The implementation assumes a 7 day week, that the first day-of-month
- * has the value 1, and that first day-of-year has the value 1.
+ * has the value 1, that first day-of-year has the value 1, and that the
+ * first of the month and year always exists.
*
* @param fieldValues the map of fields to values, which can be updated, not null
* @param resolverStyle the requested type of resolve, not null
@@ -948,8 +1024,47 @@
}
// fix proleptic month before inventing era
+ resolveProlepticMonth(fieldValues, resolverStyle);
+
+ // invent era if necessary to resolve year-of-era
+ resolveYearOfEra(fieldValues, resolverStyle);
+
+ // build date
+ if (fieldValues.containsKey(YEAR)) {
+ if (fieldValues.containsKey(MONTH_OF_YEAR)) {
+ if (fieldValues.containsKey(DAY_OF_MONTH)) {
+ return resolveYMD(fieldValues, resolverStyle);
+ }
+ if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
+ if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
+ return resolveYMAA(fieldValues, resolverStyle);
+ }
+ if (fieldValues.containsKey(DAY_OF_WEEK)) {
+ return resolveYMAD(fieldValues, resolverStyle);
+ }
+ }
+ }
+ if (fieldValues.containsKey(DAY_OF_YEAR)) {
+ return resolveYD(fieldValues, resolverStyle);
+ }
+ if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) {
+ if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
+ return resolveYAA(fieldValues, resolverStyle);
+ }
+ if (fieldValues.containsKey(DAY_OF_WEEK)) {
+ return resolveYAD(fieldValues, resolverStyle);
+ }
+ }
+ }
+ return null;
+ }
+
+ void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
Long pMonth = fieldValues.remove(PROLEPTIC_MONTH);
if (pMonth != null) {
+ if (resolverStyle != ResolverStyle.LENIENT) {
+ PROLEPTIC_MONTH.checkValidValue(pMonth);
+ }
// first day-of-month is likely to be safest for setting proleptic-month
// cannot add to year zero, as not all chronologies have a year zero
ChronoLocalDate<?> chronoDate = dateNow()
@@ -957,81 +1072,152 @@
addFieldValue(fieldValues, MONTH_OF_YEAR, chronoDate.get(MONTH_OF_YEAR));
addFieldValue(fieldValues, YEAR, chronoDate.get(YEAR));
}
+ }
- // invent era if necessary to resolve year-of-era
+ void resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
if (yoeLong != null) {
Long eraLong = fieldValues.remove(ERA);
- int yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA);
+ int yoe;
+ if (resolverStyle != ResolverStyle.LENIENT) {
+ yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA);
+ } else {
+ yoe = Math.toIntExact(yoeLong);
+ }
if (eraLong != null) {
- Era eraObj = eraOf(Math.toIntExact(eraLong));
+ Era eraObj = eraOf(range(ERA).checkValidIntValue(eraLong, ERA));
addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
- } else if (fieldValues.containsKey(YEAR)) {
- int year = range(YEAR).checkValidIntValue(fieldValues.get(YEAR), YEAR);
- ChronoLocalDate<?> chronoDate = dateYearDay(year, 1);
- addFieldValue(fieldValues, YEAR, prolepticYear(chronoDate.getEra(), yoe));
} else {
- List<Era> eras = eras();
- if (eras.isEmpty()) {
- addFieldValue(fieldValues, YEAR, yoe);
+ if (fieldValues.containsKey(YEAR)) {
+ int year = range(YEAR).checkValidIntValue(fieldValues.get(YEAR), YEAR);
+ ChronoLocalDate<?> chronoDate = dateYearDay(year, 1);
+ addFieldValue(fieldValues, YEAR, prolepticYear(chronoDate.getEra(), yoe));
+ } else if (resolverStyle == ResolverStyle.STRICT) {
+ // do not invent era if strict
+ // reinstate the field removed earlier, no cross-check issues
+ fieldValues.put(YEAR_OF_ERA, yoeLong);
} else {
- Era eraObj = eras.get(eras.size() - 1);
- addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
- }
- }
- }
-
- // build date
- if (fieldValues.containsKey(YEAR)) {
- if (fieldValues.containsKey(MONTH_OF_YEAR)) {
- if (fieldValues.containsKey(DAY_OF_MONTH)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
- int dom = range(DAY_OF_MONTH).checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH);
- return date(y, moy, dom);
- }
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
- int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
- int ad = range(ALIGNED_DAY_OF_WEEK_IN_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), ALIGNED_DAY_OF_WEEK_IN_MONTH);
- ChronoLocalDate<?> chronoDate = date(y, moy, 1);
- return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
- }
- if (fieldValues.containsKey(DAY_OF_WEEK)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
- int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
- int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
- ChronoLocalDate<?> chronoDate = date(y, moy, 1);
- return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
+ List<Era> eras = eras();
+ if (eras.isEmpty()) {
+ addFieldValue(fieldValues, YEAR, yoe);
+ } else {
+ Era eraObj = eras.get(eras.size() - 1);
+ addFieldValue(fieldValues, YEAR, prolepticYear(eraObj, yoe));
}
}
}
- if (fieldValues.containsKey(DAY_OF_YEAR)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR);
- return dateYearDay(y, doy);
- }
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) {
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
- int ad = range(ALIGNED_DAY_OF_WEEK_IN_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), ALIGNED_DAY_OF_WEEK_IN_YEAR);
- ChronoLocalDate<?> chronoDate = dateYearDay(y, 1);
- return chronoDate.plus((aw - 1) * 7 + (ad - 1), ChronoUnit.DAYS);
- }
- if (fieldValues.containsKey(DAY_OF_WEEK)) {
- int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
- int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
- int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
- ChronoLocalDate<?> chronoDate = dateYearDay(y, 1);
- return chronoDate.plus((aw - 1) * 7, ChronoUnit.DAYS).with(nextOrSame(DayOfWeek.of(dow)));
- }
+ } else if (fieldValues.containsKey(ERA)) {
+ range(ERA).checkValidValue(fieldValues.get(ERA), ERA); // always validated
+ }
+ }
+
+ ChronoLocalDate<?> resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
+ long days = Math.subtractExact(fieldValues.remove(DAY_OF_MONTH), 1);
+ return date(y, 1, 1).plus(months, MONTHS).plus(days, DAYS);
+ }
+ int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+ ValueRange domRange = range(DAY_OF_MONTH);
+ int dom = domRange.checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH);
+ if (resolverStyle == ResolverStyle.SMART) { // previous valid
+ if (dom > domRange.getSmallestMaximum()) {
+ dom = (int) domRange.getSmallestMaximum();
+ return date(y, moy, dom).with(TemporalAdjuster.lastDayOfMonth());
}
}
- return null;
+ return date(y, moy, dom);
+ }
+
+ ChronoLocalDate<?> resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1);
+ return dateYearDay(y, 1).plus(days, DAYS);
+ }
+ int doy = range(DAY_OF_YEAR).checkValidIntValue(fieldValues.remove(DAY_OF_YEAR), DAY_OF_YEAR);
+ return dateYearDay(y, doy); // smart is same as strict
+ }
+
+ ChronoLocalDate<?> resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
+ long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1);
+ return date(y, 1, 1).plus(months, MONTHS).plus(weeks, WEEKS).plus(days, DAYS);
+ }
+ int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+ int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
+ int ad = range(ALIGNED_DAY_OF_WEEK_IN_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), ALIGNED_DAY_OF_WEEK_IN_MONTH);
+ ChronoLocalDate<?> date = date(y, moy, 1).plus((aw - 1) * 7 + (ad - 1), DAYS);
+ if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) {
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
+ }
+ return date;
+ }
+
+ ChronoLocalDate<?> resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
+ long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
+ return resolveAligned(date(y, 1, 1), months, weeks, dow);
+ }
+ int moy = range(MONTH_OF_YEAR).checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR), MONTH_OF_YEAR);
+ int aw = range(ALIGNED_WEEK_OF_MONTH).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), ALIGNED_WEEK_OF_MONTH);
+ int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
+ ChronoLocalDate<?> date = date(y, moy, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow)));
+ if (resolverStyle == ResolverStyle.STRICT && date.get(MONTH_OF_YEAR) != moy) {
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
+ }
+ return date;
+ }
+
+ ChronoLocalDate<?> resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
+ long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1);
+ return dateYearDay(y, 1).plus(weeks, WEEKS).plus(days, DAYS);
+ }
+ int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
+ int ad = range(ALIGNED_DAY_OF_WEEK_IN_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), ALIGNED_DAY_OF_WEEK_IN_YEAR);
+ ChronoLocalDate<?> date = dateYearDay(y, 1).plus((aw - 1) * 7 + (ad - 1), DAYS);
+ if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) {
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
+ }
+ return date;
+ }
+
+ ChronoLocalDate<?> resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ int y = range(YEAR).checkValidIntValue(fieldValues.remove(YEAR), YEAR);
+ if (resolverStyle == ResolverStyle.LENIENT) {
+ long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
+ long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
+ return resolveAligned(dateYearDay(y, 1), 0, weeks, dow);
+ }
+ int aw = range(ALIGNED_WEEK_OF_YEAR).checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), ALIGNED_WEEK_OF_YEAR);
+ int dow = range(DAY_OF_WEEK).checkValidIntValue(fieldValues.remove(DAY_OF_WEEK), DAY_OF_WEEK);
+ ChronoLocalDate<?> date = dateYearDay(y, 1).plus((aw - 1) * 7, DAYS).with(nextOrSame(DayOfWeek.of(dow)));
+ if (resolverStyle == ResolverStyle.STRICT && date.get(YEAR) != y) {
+ throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
+ }
+ return date;
+ }
+
+ ChronoLocalDate<?> resolveAligned(ChronoLocalDate<?> base, long months, long weeks, long dow) {
+ ChronoLocalDate<?> date = base.plus(months, MONTHS).plus(weeks, WEEKS);
+ if (dow > 7) {
+ date = date.plus((dow - 1) / 7, WEEKS);
+ dow = ((dow - 1) % 7) + 1;
+ } else if (dow < 1) {
+ date = date.plus(Math.subtractExact(dow, 7) / 7, WEEKS);
+ dow = ((dow + 6) % 7) + 1;
+ }
+ return date.with(nextOrSame(DayOfWeek.of((int) dow)));
}
/**
diff -r 9b12f3b38e14 -r bf5678a8051f src/share/classes/java/time/chrono/IsoChronology.java
--- a/src/share/classes/java/time/chrono/IsoChronology.java Fri May 31 00:15:00 2013 +0100
+++ b/src/share/classes/java/time/chrono/IsoChronology.java Fri May 31 00:29:36 2013 +0100
@@ -61,25 +61,16 @@
*/
package java.time.chrono;
-import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH;
-import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR;
-import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH;
-import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR;
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;
-import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.ERA;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
import static java.time.temporal.ChronoField.YEAR;
import static java.time.temporal.ChronoField.YEAR_OF_ERA;
-import static java.time.temporal.TemporalAdjuster.nextOrSame;
import java.io.Serializable;
import java.time.Clock;
import java.time.DateTimeException;
-import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -421,7 +412,7 @@
* as follows.
* <ul>
* <li>{@code EPOCH_DAY} - If present, this is converted to a {@code LocalDate}
- * all other date fields are then cross-checked against the date
+ * and all other date fields are then cross-checked against the date.
* <li>{@code PROLEPTIC_MONTH} - If present, then it is split into the
* {@code YEAR} and {@code MONTH_OF_YEAR}. If the mode is strict or smart
* then the field is validated.
@@ -430,7 +421,7 @@
* range is not validated, in smart and strict mode it is. The {@code ERA} is
* validated for range in all three modes. If only the {@code YEAR_OF_ERA} is
* present, and the mode is smart or lenient, then the current era (CE/AD)
- * is assumed. In strict mode, no ers is assumed and the {@code YEAR_OF_ERA} is
+ * is assumed. In strict mode, no era is assumed and the {@code YEAR_OF_ERA} is
* left untouched. If only the {@code ERA} is present, then it is left untouched.
* <li>{@code YEAR}, {@code MONTH_OF_YEAR} and {@code DAY_OF_MONTH} -
* If all three are present, then they are combined to form a {@code LocalDate}.
@@ -495,48 +486,11 @@
*/
@Override // override for performance
public LocalDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- // check epoch-day before inventing era
- if (fieldValues.containsKey(EPOCH_DAY)) {
- return LocalDate.ofEpochDay(fieldValues.remove(EPOCH_DAY));
- }
-
- // fix proleptic month before inventing era
- resolveProlepticMonth(fieldValues, resolverStyle);
-
- // invent era if necessary to resolve year-of-era
- resolveYearOfEra(fieldValues, resolverStyle);
-
- // build date
- if (fieldValues.containsKey(YEAR)) {
- if (fieldValues.containsKey(MONTH_OF_YEAR)) {
- if (fieldValues.containsKey(DAY_OF_MONTH)) {
- return resolveYMD(fieldValues, resolverStyle);
- }
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_MONTH)) {
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_MONTH)) {
- return resolveYMAA(fieldValues, resolverStyle);
- }
- if (fieldValues.containsKey(DAY_OF_WEEK)) {
- return resolveYMAD(fieldValues, resolverStyle);
- }
- }
- }
- if (fieldValues.containsKey(DAY_OF_YEAR)) {
- return resolveYD(fieldValues, resolverStyle);
- }
- if (fieldValues.containsKey(ALIGNED_WEEK_OF_YEAR)) {
- if (fieldValues.containsKey(ALIGNED_DAY_OF_WEEK_IN_YEAR)) {
- return resolveYAA(fieldValues, resolverStyle);
- }
- if (fieldValues.containsKey(DAY_OF_WEEK)) {
- return resolveYAD(fieldValues, resolverStyle);
- }
- }
- }
- return null;
+ return (LocalDate) super.resolveDate(fieldValues, resolverStyle);
}
- private void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ @Override // override for better proleptic algorithm
+ void resolveProlepticMonth(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
Long pMonth = fieldValues.remove(PROLEPTIC_MONTH);
if (pMonth != null) {
if (resolverStyle != ResolverStyle.LENIENT) {
@@ -547,7 +501,8 @@
}
}
- private void resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ @Override // override for enhanced behaviour
+ void resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
if (yoeLong != null) {
if (resolverStyle != ResolverStyle.LENIENT) {
@@ -575,10 +530,13 @@
} else {
throw new DateTimeException("Invalid value for era: " + era);
}
+ } else if (fieldValues.containsKey(ERA)) {
+ ERA.checkValidValue(fieldValues.get(ERA)); // always validated
}
}
- private LocalDate resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+ @Override // override for performance
+ LocalDate resolveYMD(Map <TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
if (resolverStyle == ResolverStyle.LENIENT) {
long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
@@ -598,96 +556,6 @@
return LocalDate.of(y, moy, dom);
}
- private LocalDate resolveYD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
- if (resolverStyle == ResolverStyle.LENIENT) {
- long days = Math.subtractExact(fieldValues.remove(DAY_OF_YEAR), 1);
- return LocalDate.of(y, 1, 1).plusDays(days);
- }
- int doy = DAY_OF_YEAR.checkValidIntValue(fieldValues.remove(DAY_OF_YEAR));
- return LocalDate.ofYearDay(y, doy); // smart is same as strict
- }
-
- private LocalDate resolveYMAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
- if (resolverStyle == ResolverStyle.LENIENT) {
- long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
- long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH), 1);
- return LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks).plusDays(days);
- }
- int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
- int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH));
- int ad = ALIGNED_DAY_OF_WEEK_IN_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_MONTH));
- LocalDate date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7 + (ad - 1));
- if (resolverStyle == ResolverStyle.STRICT && date.getMonthValue() != moy) {
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
- }
- return date;
- }
-
- private LocalDate resolveYMAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
- if (resolverStyle == ResolverStyle.LENIENT) {
- long months = Math.subtractExact(fieldValues.remove(MONTH_OF_YEAR), 1);
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_MONTH), 1);
- long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
- return resolveAligned(y, months, weeks, dow);
- }
- int moy = MONTH_OF_YEAR.checkValidIntValue(fieldValues.remove(MONTH_OF_YEAR));
- int aw = ALIGNED_WEEK_OF_MONTH.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_MONTH));
- int dow = DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(DAY_OF_WEEK));
- LocalDate date = LocalDate.of(y, moy, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)));
- if (resolverStyle == ResolverStyle.STRICT && date.getMonthValue() != moy) {
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different month");
- }
- return date;
- }
-
- private LocalDate resolveYAA(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
- if (resolverStyle == ResolverStyle.LENIENT) {
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
- long days = Math.subtractExact(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR), 1);
- return LocalDate.of(y, 1, 1).plusWeeks(weeks).plusDays(days);
- }
- int aw = ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR));
- int ad = ALIGNED_DAY_OF_WEEK_IN_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_DAY_OF_WEEK_IN_YEAR));
- LocalDate date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7 + (ad - 1));
- if (resolverStyle == ResolverStyle.STRICT && date.getYear() != y) {
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
- }
- return date;
- }
-
- private LocalDate resolveYAD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
- int y = YEAR.checkValidIntValue(fieldValues.remove(YEAR));
- if (resolverStyle == ResolverStyle.LENIENT) {
- long weeks = Math.subtractExact(fieldValues.remove(ALIGNED_WEEK_OF_YEAR), 1);
- long dow = Math.subtractExact(fieldValues.remove(DAY_OF_WEEK), 1);
- return resolveAligned(y, 0, weeks, dow);
- }
- int aw = ALIGNED_WEEK_OF_YEAR.checkValidIntValue(fieldValues.remove(ALIGNED_WEEK_OF_YEAR));
- int dow = DAY_OF_WEEK.checkValidIntValue(fieldValues.remove(DAY_OF_WEEK));
- LocalDate date = LocalDate.of(y, 1, 1).plusDays((aw - 1) * 7).with(nextOrSame(DayOfWeek.of(dow)));
- if (resolverStyle == ResolverStyle.STRICT && date.getYear() != y) {
- throw new DateTimeException("Strict mode rejected resolved date as it is in a different year");
- }
- return date;
- }
-
- private LocalDate resolveAligned(int y, long months, long weeks, long dow) {
- LocalDate date = LocalDate.of(y, 1, 1).plusMonths(months).plusWeeks(weeks);
- 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;
- }
- return date.with(nextOrSame(DayOfWeek.of((int) dow)));
- }
-
//-----------------------------------------------------------------------
@Override
public ValueRange range(ChronoField field) {
diff -r 9b12f3b38e14 -r bf5678a8051f test/java/time/tck/java/time/chrono/TCKIsoChronology.java
--- a/test/java/time/tck/java/time/chrono/TCKIsoChronology.java Fri May 31 00:15:00 2013 +0100
+++ b/test/java/time/tck/java/time/chrono/TCKIsoChronology.java Fri May 31 00:29:36 2013 +0100
@@ -278,91 +278,88 @@
@DataProvider(name = "resolve_yearOfEra")
Object[][] data_resolve_yearOfEra() {
return new Object[][] {
- {-1, 2012, null, null, false, false},
- {0, 2012, null, -2011, true, true},
- {1, 2012, null, 2012, true, true},
- {2, 2012, null, null, false, false},
+ // era only
+ {ResolverStyle.STRICT, -1, null, null, null, null},
+ {ResolverStyle.SMART, -1, null, null, null, null},
+ {ResolverStyle.LENIENT, -1, null, null, null, null},
- {null, 2012, null, 2012, true, null},
- {null, 2012, 2012, 2012, true, true},
- {null, 2012, -2011, -2011, true, true},
- {null, 2012, 2013, null, false, false},
- {null, 2012, -2013, null, false, false},
+ {ResolverStyle.STRICT, 0, null, null, ChronoField.ERA, 0},
+ {ResolverStyle.SMART, 0, null, null, ChronoField.ERA, 0},
+ {ResolverStyle.LENIENT, 0, null, null, ChronoField.ERA, 0},
+
+ {ResolverStyle.STRICT, 1, null, null, ChronoField.ERA, 1},
+ {ResolverStyle.SMART, 1, null, null, ChronoField.ERA, 1},
+ {ResolverStyle.LENIENT, 1, null, null, ChronoField.ERA, 1},
+
+ {ResolverStyle.STRICT, 2, null, null, null, null},
+ {ResolverStyle.SMART, 2, null, null, null, null},
+ {ResolverStyle.LENIENT, 2, null, null, null, null},
+
+ // era and year-of-era
+ {ResolverStyle.STRICT, -1, 2012, null, null, null},
+ {ResolverStyle.SMART, -1, 2012, null, null, null},
+ {ResolverStyle.LENIENT, -1, 2012, null, null, null},
+
+ {ResolverStyle.STRICT, 0, 2012, null, ChronoField.YEAR, -2011},
+ {ResolverStyle.SMART, 0, 2012, null, ChronoField.YEAR, -2011},
+ {ResolverStyle.LENIENT, 0, 2012, null, ChronoField.YEAR, -2011},
+
+ {ResolverStyle.STRICT, 1, 2012, null, ChronoField.YEAR, 2012},
+ {ResolverStyle.SMART, 1, 2012, null, ChronoField.YEAR, 2012},
+ {ResolverStyle.LENIENT, 1, 2012, null, ChronoField.YEAR, 2012},
+
+ {ResolverStyle.STRICT, 2, 2012, null, null, null},
+ {ResolverStyle.SMART, 2, 2012, null, null, null},
+ {ResolverStyle.LENIENT, 2, 2012, null, null, null},
+
+ // year-of-era only
+ {ResolverStyle.STRICT, null, 2012, null, ChronoField.YEAR_OF_ERA, 2012},
+ {ResolverStyle.SMART, null, 2012, null, ChronoField.YEAR, 2012},
+ {ResolverStyle.LENIENT, null, 2012, null, ChronoField.YEAR, 2012},
+
+ {ResolverStyle.STRICT, null, Integer.MAX_VALUE, null, null, null},
+ {ResolverStyle.SMART, null, Integer.MAX_VALUE, null, null, null},
+ {ResolverStyle.LENIENT, null, Integer.MAX_VALUE, null, ChronoField.YEAR, Integer.MAX_VALUE},
+
+ // year-of-era and year
+ {ResolverStyle.STRICT, null, 2012, 2012, ChronoField.YEAR, 2012},
+ {ResolverStyle.SMART, null, 2012, 2012, ChronoField.YEAR, 2012},
+ {ResolverStyle.LENIENT, null, 2012, 2012, ChronoField.YEAR, 2012},
+
+ {ResolverStyle.STRICT, null, 2012, -2011, ChronoField.YEAR, -2011},
+ {ResolverStyle.SMART, null, 2012, -2011, ChronoField.YEAR, -2011},
+ {ResolverStyle.LENIENT, null, 2012, -2011, ChronoField.YEAR, -2011},
+
+ {ResolverStyle.STRICT, null, 2012, 2013, null, null},
+ {ResolverStyle.SMART, null, 2012, 2013, null, null},
+ {ResolverStyle.LENIENT, null, 2012, 2013, null, null},
+
+ {ResolverStyle.STRICT, null, 2012, -2013, null, null},
+ {ResolverStyle.SMART, null, 2012, -2013, null, null},
+ {ResolverStyle.LENIENT, null, 2012, -2013, null, null},
};
}
@Test(dataProvider = "resolve_yearOfEra")
- public void test_resolve_yearOfEra_lenient(Integer e, int yoe, Integer y, Integer expected, boolean smart, Boolean strict) {
+ public void test_resolve_yearOfEra(ResolverStyle style, Integer e, Integer yoe, Integer y, ChronoField field, Integer expected) {
Map<TemporalField, Long> fieldValues = new HashMap<>();
if (e != null) {
fieldValues.put(ChronoField.ERA, (long) e);
}
- fieldValues.put(ChronoField.YEAR_OF_ERA, (long) yoe);
+ if (yoe != null) {
+ fieldValues.put(ChronoField.YEAR_OF_ERA, (long) yoe);
+ }
if (y != null) {
fieldValues.put(ChronoField.YEAR, (long) y);
}
- if (smart) {
- LocalDate date = IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.LENIENT);
+ if (field != null) {
+ LocalDate date = IsoChronology.INSTANCE.resolveDate(fieldValues, style);
assertEquals(date, null);
- assertEquals(fieldValues.get(ChronoField.YEAR), (Long) (long) expected);
+ assertEquals(fieldValues.get(field), (Long) expected.longValue());
assertEquals(fieldValues.size(), 1);
} else {
try {
- IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.LENIENT);
- fail("Should have failed");
- } catch (DateTimeException ex) {
- // expected
- }
- }
- }
-
- @Test(dataProvider = "resolve_yearOfEra")
- public void test_resolve_yearOfEra_smart(Integer e, int yoe, Integer y, Integer expected, boolean smart, Boolean strict) {
- Map<TemporalField, Long> fieldValues = new HashMap<>();
- if (e != null) {
- fieldValues.put(ChronoField.ERA, (long) e);
- }
- fieldValues.put(ChronoField.YEAR_OF_ERA, (long) yoe);
- if (y != null) {
- fieldValues.put(ChronoField.YEAR, (long) y);
- }
- if (smart) {
- LocalDate date = IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
- assertEquals(date, null);
- assertEquals(fieldValues.get(ChronoField.YEAR), (Long) (long) expected);
- assertEquals(fieldValues.size(), 1);
- } else {
- try {
- IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
- fail("Should have failed");
- } catch (DateTimeException ex) {
- // expected
- }
- }
- }
-
- @Test(dataProvider = "resolve_yearOfEra")
- public void test_resolve_yearOfEra_strict(Integer e, int yoe, Integer y, Integer expected, boolean smart, Boolean strict) {
- Map<TemporalField, Long> fieldValues = new HashMap<>();
- if (e != null) {
- fieldValues.put(ChronoField.ERA, (long) e);
- }
- fieldValues.put(ChronoField.YEAR_OF_ERA, (long) yoe);
- if (y != null) {
- fieldValues.put(ChronoField.YEAR, (long) y);
- }
- if (strict == null) {
- LocalDate date = IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
- assertEquals(date, null);
- assertEquals(fieldValues.get(ChronoField.YEAR_OF_ERA), (Long) (long) yoe);
- } else if (strict) {
- LocalDate date = IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
- assertEquals(date, null);
- assertEquals(fieldValues.get(ChronoField.YEAR), (Long) (long) expected);
- assertEquals(fieldValues.size(), 1);
- } else {
- try {
- IsoChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
+ IsoChronology.INSTANCE.resolveDate(fieldValues, style);
fail("Should have failed");
} catch (DateTimeException ex) {
// expected
Lacks tests for non-ISO calendars. Japanese still needs to handle eras differently, others should work.
The approach is practical and the implementation straightforward. Looks fine to me.
See http://hg.openjdk.java.net/threeten/threeten/jdk/rev/bf5678a8051f
Still needs non-ISO tests
The patch looks fine. Thanks
Allow lenient/strict/smart parsing of non-ISO chronologies. Breakout from #282