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 other non-ISO #318

Closed jodastephen closed 11 years ago

jodastephen commented 11 years ago

Allow lenient/strict/smart parsing of non-ISO chronologies. Breakout from #282

jodastephen commented 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
jodastephen commented 11 years ago

Lacks tests for non-ISO calendars. Japanese still needs to handle eras differently, others should work.

RogerRiggs commented 11 years ago

The approach is practical and the implementation straightforward. Looks fine to me.

jodastephen commented 11 years ago

See http://hg.openjdk.java.net/threeten/threeten/jdk/rev/bf5678a8051f

Still needs non-ISO tests

jodastephen commented 11 years ago

Patch https://gist.github.com/jodastephen/5848884

RogerRiggs commented 11 years ago

The patch looks fine. Thanks

jodastephen commented 11 years ago

http://hg.openjdk.java.net/threeten/threeten/jdk/rev/f7573f51e6f9