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

Fix implementation of resolving date in Japanese chronology #299

Closed jodastephen closed 10 years ago

jodastephen commented 11 years ago

The Japanese chronology has two issues in using the standard resolving code.

1) The year-of-era and era must be merged with more care, as there needs to be a way to validate that the year-month-day is correct for the era. This will need a strict/lenient flag.

2) The day-of-year counts from the calendar year, rather than any new year caused by the change of era. While unusual compared to other calendar systems, it needs correcting, see #272.

Note that Chronology.dateYearDay must also be consistent with the mid-year new year behaviour.

RogerRiggs commented 11 years ago

Since #272 is closed, does the javadoc describing DAY_OF_YEAR need to be updated?

jodastephen commented 11 years ago

Maybe a little. It should indicate that it may vary in calendars with era changes part way through a year. (But since the same might be true of an odd calendar restarting months and day-of-month as well, these things get hard to define, or so weak that the definition is meaningless.

RogerRiggs commented 11 years ago

The description of DAY_OF_YEAR in ChronoField is sufficient and refers to the Chronology where appropriate. For the JapaneseChronology, a description of the behavior should be added to dateYearDay(era, yearOfEra, dayOfYear such as: "The day-of-year for year-of-era 1 is counted from the first day of the era; for subsequent years, it is counted from the month 1, day 1."

jodastephen commented 11 years ago

Please raise a new issue so this doesn't get forgotten.

RogerRiggs commented 11 years ago

The implementation has been updated to validate era/month/day correctly with a strict interpretation. With respect to 1) above and some ability to support a lenient interpretation, what needs to be done in JapaneseChronology?

jodastephen commented 11 years ago

The resolveDate() method in JapaneseChronology needs to be overridden to handle this when parsing. The extra logic should also handle lenient/strict, such that 1st Jan is always valid in lenient mode, but invalid in start of era years in strict mode. Same for 31st Dec.

jodastephen commented 11 years ago

Proposed behaviour for Japanese dates during parsing is as follows:

Given these dates in 1989: SHOWA 64-01-06 SHOWA 64-01-07 HEISEI 1-01-08 HEISEI 1-01-09

jodastephen commented 11 years ago
# HG changeset patch
# User scolebourne
# Date 1371837629 -3600
# Node ID fbac779a293ec66a1f141d22b7a10e9078677c07
# Parent  f7573f51e6f97ad8f3610797215c45a37ced541c
Enhance Japanese calendar system strict/lenient/smart implementation
See #299

diff --git a/src/share/classes/java/time/chrono/Chronology.java b/src/share/classes/java/time/chrono/Chronology.java
--- a/src/share/classes/java/time/chrono/Chronology.java
+++ b/src/share/classes/java/time/chrono/Chronology.java
@@ -1027,7 +1027,10 @@
         resolveProlepticMonth(fieldValues, resolverStyle);

         // invent era if necessary to resolve year-of-era
-        resolveYearOfEra(fieldValues, resolverStyle);
+        ChronoLocalDate<?> resolved = resolveYearOfEra(fieldValues, resolverStyle);
+        if (resolved != null) {
+            return resolved;
+        }

         // build date
         if (fieldValues.containsKey(YEAR)) {
@@ -1074,7 +1077,7 @@
         }
     }

-    void resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+    ChronoLocalDate<?> resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
         Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
         if (yoeLong != null) {
             Long eraLong = fieldValues.remove(ERA);
@@ -1109,6 +1112,7 @@
         } else if (fieldValues.containsKey(ERA)) {
             range(ERA).checkValidValue(fieldValues.get(ERA), ERA);  // always validated
         }
+        return null;
     }

     ChronoLocalDate<?> resolveYMD(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
diff --git a/src/share/classes/java/time/chrono/IsoChronology.java b/src/share/classes/java/time/chrono/IsoChronology.java
--- a/src/share/classes/java/time/chrono/IsoChronology.java
+++ b/src/share/classes/java/time/chrono/IsoChronology.java
@@ -502,7 +502,7 @@
     }

     @Override  // override for enhanced behaviour
-    void resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+    LocalDate resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
         Long yoeLong = fieldValues.remove(YEAR_OF_ERA);
         if (yoeLong != null) {
             if (resolverStyle != ResolverStyle.LENIENT) {
@@ -533,6 +533,7 @@
         } else if (fieldValues.containsKey(ERA)) {
             ERA.checkValidValue(fieldValues.get(ERA));  // always validated
         }
+        return null;
     }

     @Override  // override for performance
diff --git a/src/share/classes/java/time/chrono/JapaneseChronology.java b/src/share/classes/java/time/chrono/JapaneseChronology.java
--- a/src/share/classes/java/time/chrono/JapaneseChronology.java
+++ b/src/share/classes/java/time/chrono/JapaneseChronology.java
@@ -56,6 +56,15 @@
  */
 package java.time.chrono;

+import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.DAY_OF_YEAR;
+import static java.time.temporal.ChronoField.ERA;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+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 java.io.Serializable;
 import java.time.Clock;
 import java.time.DateTimeException;
@@ -66,6 +75,7 @@
 import java.time.format.ResolverStyle;
 import java.time.temporal.ChronoField;
 import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalAdjuster;
 import java.time.temporal.TemporalField;
 import java.time.temporal.ValueRange;
 import java.util.Arrays;
@@ -214,8 +224,7 @@
      */
     @Override
     public JapaneseDate dateYearDay(int prolepticYear, int dayOfYear) {
-        LocalDate date = LocalDate.ofYearDay(prolepticYear, dayOfYear);
-        return date(prolepticYear, date.getMonthValue(), date.getDayOfMonth());
+        return new JapaneseDate(LocalDate.ofYearDay(prolepticYear, dayOfYear));
     }

     /**
@@ -400,4 +409,87 @@
         return (JapaneseDate) super.resolveDate(fieldValues, resolverStyle);
     }

+    @Override  // override for special Japanese behavior
+    ChronoLocalDate<?> resolveYearOfEra(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
+        // validate era and year-of-era
+        Long eraLong = fieldValues.get(ERA);
+        JapaneseEra era = null;
+        if (eraLong != null) {
+            era = eraOf(range(ERA).checkValidIntValue(eraLong, ERA));  // always validated
+        }
+        Long yoeLong = fieldValues.get(YEAR_OF_ERA);
+        int yoe = 0;
+        if (yoeLong != null) {
+            yoe = range(YEAR_OF_ERA).checkValidIntValue(yoeLong, YEAR_OF_ERA);  // always validated
+        }
+        // if only year-of-era and no year then invent era unless strict
+        if (era == null && yoeLong != null && fieldValues.containsKey(YEAR) == false && resolverStyle != ResolverStyle.STRICT) {
+            era = JapaneseEra.values()[JapaneseEra.values().length - 1];
+        }
+        // if both present, then try to create date
+        if (yoeLong != null && era != null) {
+            if (fieldValues.containsKey(MONTH_OF_YEAR)) {
+                if (fieldValues.containsKey(DAY_OF_MONTH)) {
+                    return resolveYMD(yoe, era, fieldValues, resolverStyle);
+                }
+            }
+            if (fieldValues.containsKey(DAY_OF_YEAR)) {
+                return resolveYD(yoe, era, fieldValues, resolverStyle);
+            }
+        }
+        return null;
+    }
+
+    private int prolepticYearLenient(JapaneseEra era, int yearOfEra) {
+        if (era == JapaneseEra.SEIREKI) {
+            return yearOfEra;
+        }
+        return era.getPrivateEra().getSinceDate().getYear() + yearOfEra - 1;
+    }
+
+     private ChronoLocalDate<?> resolveYMD(int yoe, JapaneseEra era, Map<TemporalField,Long> fieldValues, ResolverStyle resolverStyle) {
+         fieldValues.remove(ERA);
+         fieldValues.remove(YEAR_OF_ERA);
+         if (resolverStyle == ResolverStyle.LENIENT) {
+             int y = prolepticYearLenient(era, yoe);
+             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);
+         int dom = range(DAY_OF_MONTH).checkValidIntValue(fieldValues.remove(DAY_OF_MONTH), DAY_OF_MONTH);
+         if (resolverStyle == ResolverStyle.SMART) {  // previous valid
+             if (yoe < 1) {
+                 throw new DateTimeException("Invalid YearOfEra: " + yoe);
+             }
+             int y = prolepticYearLenient(era, yoe);
+             JapaneseDate result;
+             try {
+                 result = date(y, moy, dom);
+             } catch (DateTimeException ex) {
+                 result = date(y, moy, 1).with(TemporalAdjuster.lastDayOfMonth());
+             }
+             // handle the era being changed
+             // only allow if the new date is in the same Jan-Dec as the era change
+             // determine by ensuring either original yoe or result yoe is 1
+             if (result.getEra() != era && result.get(YEAR_OF_ERA) > 1 && yoe > 1) {
+                 throw new DateTimeException("Invalid YearOfEra for Era: " + era + " " + yoe);
+             }
+             return result;
+         }
+         return date(era, yoe, moy, dom);
+     }
+
+    private ChronoLocalDate<?> resolveYD(int yoe, JapaneseEra era, Map <TemporalField,Long> fieldValues, ResolverStyle resolverStyle) {
+        fieldValues.remove(ERA);
+        fieldValues.remove(YEAR_OF_ERA);
+        if (resolverStyle == ResolverStyle.LENIENT) {
+            int y = prolepticYearLenient(era, yoe);
+            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(era, yoe, doy);  // smart is same as strict
+    }
+
 }
diff --git a/src/share/classes/java/time/chrono/JapaneseDate.java b/src/share/classes/java/time/chrono/JapaneseDate.java
--- a/src/share/classes/java/time/chrono/JapaneseDate.java
+++ b/src/share/classes/java/time/chrono/JapaneseDate.java
@@ -69,7 +69,6 @@
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.Period;
-import java.time.Year;
 import java.time.ZoneId;
 import java.time.temporal.ChronoField;
 import java.time.temporal.TemporalAccessor;
diff --git a/test/java/time/tck/java/time/chrono/TCKJapaneseChronology.java b/test/java/time/tck/java/time/chrono/TCKJapaneseChronology.java
--- a/test/java/time/tck/java/time/chrono/TCKJapaneseChronology.java
+++ b/test/java/time/tck/java/time/chrono/TCKJapaneseChronology.java
@@ -85,11 +85,15 @@
 import java.time.chrono.JapaneseEra;
 import java.time.chrono.MinguoChronology;
 import java.time.chrono.MinguoDate;
+import java.time.format.ResolverStyle;
 import java.time.temporal.ChronoField;
 import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalField;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;

 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
@@ -599,4 +603,477 @@
         assertFalse(JapaneseChronology.INSTANCE.equals(IsoChronology.INSTANCE));
     }

+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    @DataProvider(name = "resolve_styleByEra")
+    Object[][] data_resolve_styleByEra() {
+        Object[][] result = new Object[ResolverStyle.values().length * JapaneseEra.values().length][];
+        int i = 0;
+        for (ResolverStyle style : ResolverStyle.values()) {
+            for (JapaneseEra era : JapaneseEra.values()) {
+                result[i++] = new Object[] {style, era};
+            }
+        }
+        return result;
+    }
+
+    @Test(dataProvider = "resolve_styleByEra")
+    public void test_resolve_yearOfEra_eraOnly_valid(ResolverStyle style, JapaneseEra era) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.ERA, (long) era.getValue());
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+        assertEquals(date, null);
+        assertEquals(fieldValues.get(ChronoField.ERA), (Long) (long) era.getValue());
+        assertEquals(fieldValues.size(), 1);
+    }
+
+    @Test(dataProvider = "resolve_styleByEra")
+    public void test_resolve_yearOfEra_eraAndYearOfEraOnly_valid(ResolverStyle style, JapaneseEra era) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.ERA, (long) era.getValue());
+        fieldValues.put(ChronoField.YEAR_OF_ERA, 1L);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+        assertEquals(date, null);
+        assertEquals(fieldValues.get(ChronoField.ERA), (Long) (long) era.getValue());
+        assertEquals(fieldValues.get(ChronoField.YEAR_OF_ERA), (Long) 1L);
+        assertEquals(fieldValues.size(), 2);
+    }
+
+    @Test(dataProvider = "resolve_styleByEra")
+    public void test_resolve_yearOfEra_eraAndYearOnly_valid(ResolverStyle style, JapaneseEra era) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.ERA, (long) era.getValue());
+        fieldValues.put(ChronoField.YEAR, 1L);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+        assertEquals(date, null);
+        assertEquals(fieldValues.get(ChronoField.ERA), (Long) (long) era.getValue());
+        assertEquals(fieldValues.get(ChronoField.YEAR), (Long) 1L);
+        assertEquals(fieldValues.size(), 2);
+    }
+
+    @DataProvider(name = "resolve_styles")
+    Object[][] data_resolve_styles() {
+        Object[][] result = new Object[ResolverStyle.values().length][];
+        int i = 0;
+        for (ResolverStyle style : ResolverStyle.values()) {
+            result[i++] = new Object[] {style};
+        }
+        return result;
+    }
+
+    @Test(dataProvider = "resolve_styles")
+    public void test_resolve_yearOfEra_yearOfEraOnly_valid(ResolverStyle style) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR_OF_ERA, 1L);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+        assertEquals(date, null);
+        assertEquals(fieldValues.get(ChronoField.YEAR_OF_ERA), (Long) 1L);
+        assertEquals(fieldValues.size(), 1);
+    }
+
+    @Test(dataProvider = "resolve_styles")
+    public void test_resolve_yearOfEra_yearOfEraAndYearOnly_valid(ResolverStyle style) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR_OF_ERA, 1L);
+        fieldValues.put(ChronoField.YEAR, 2012L);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+        assertEquals(date, null);
+        assertEquals(fieldValues.get(ChronoField.YEAR_OF_ERA), (Long) 1L);
+        assertEquals(fieldValues.get(ChronoField.YEAR), (Long) 2012L);
+        assertEquals(fieldValues.size(), 2);
+    }
+
+    public void test_resolve_yearOfEra_eraOnly_invalidTooSmall() {
+        for (ResolverStyle style : ResolverStyle.values()) {
+            Map<TemporalField, Long> fieldValues = new HashMap<>();
+            fieldValues.put(ChronoField.ERA, JapaneseEra.SEIREKI.getValue() - 1L);
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+                fail("Should have failed: " + style);
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    public void test_resolve_yearOfEra_eraOnly_invalidTooLarge() {
+        for (ResolverStyle style : ResolverStyle.values()) {
+            Map<TemporalField, Long> fieldValues = new HashMap<>();
+            fieldValues.put(ChronoField.ERA, JapaneseEra.values()[JapaneseEra.values().length - 1].getValue() + 1L);
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+                fail("Should have failed: " + style);
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    @DataProvider(name = "resolve_ymd")
+    Object[][] data_resolve_ymd() {
+        return new Object[][] {
+                {2012, 1, -365, date(2010, 12, 31), false, false},
+                {2012, 1, -364, date(2011, 1, 1), false, false},
+                {2012, 1, -31, date(2011, 11, 30), false, false},
+                {2012, 1, -30, date(2011, 12, 1), false, false},
+                {2012, 1, -12, date(2011, 12, 19), false, false},
+                {2012, 1, 1, date(2012, 1, 1), true, true},
+                {2012, 1, 59, date(2012, 2, 28), false, false},
+                {2012, 1, 60, date(2012, 2, 29), false, false},
+                {2012, 1, 61, date(2012, 3, 1), false, false},
+                {2012, 1, 365, date(2012, 12, 30), false, false},
+                {2012, 1, 366, date(2012, 12, 31), false, false},
+                {2012, 1, 367, date(2013, 1, 1), false, false},
+                {2012, 1, 367 + 364, date(2013, 12, 31), false, false},
+                {2012, 1, 367 + 365, date(2014, 1, 1), false, false},
+
+                {2012, 2, 1, date(2012, 2, 1), true, true},
+                {2012, 2, 28, date(2012, 2, 28), true, true},
+                {2012, 2, 29, date(2012, 2, 29), true, true},
+                {2012, 2, 30, date(2012, 3, 1), date(2012, 2, 29), false},
+                {2012, 2, 31, date(2012, 3, 2), date(2012, 2, 29), false},
+                {2012, 2, 32, date(2012, 3, 3), false, false},
+
+                {2012, -12, 1, date(2010, 12, 1), false, false},
+                {2012, -11, 1, date(2011, 1, 1), false, false},
+                {2012, -1, 1, date(2011, 11, 1), false, false},
+                {2012, 0, 1, date(2011, 12, 1), false, false},
+                {2012, 1, 1, date(2012, 1, 1), true, true},
+                {2012, 12, 1, date(2012, 12, 1), true, true},
+                {2012, 13, 1, date(2013, 1, 1), false, false},
+                {2012, 24, 1, date(2013, 12, 1), false, false},
+                {2012, 25, 1, date(2014, 1, 1), false, false},
+
+                {2012, 6, -31, date(2012, 4, 30), false, false},
+                {2012, 6, -30, date(2012, 5, 1), false, false},
+                {2012, 6, -1, date(2012, 5, 30), false, false},
+                {2012, 6, 0, date(2012, 5, 31), false, false},
+                {2012, 6, 1, date(2012, 6, 1), true, true},
+                {2012, 6, 30, date(2012, 6, 30), true, true},
+                {2012, 6, 31, date(2012, 7, 1), date(2012, 6, 30), false},
+                {2012, 6, 61, date(2012, 7, 31), false, false},
+                {2012, 6, 62, date(2012, 8, 1), false, false},
+
+                {2011, 2, 1, date(2011, 2, 1), true, true},
+                {2011, 2, 28, date(2011, 2, 28), true, true},
+                {2011, 2, 29, date(2011, 3, 1), date(2011, 2, 28), false},
+                {2011, 2, 30, date(2011, 3, 2), date(2011, 2, 28), false},
+                {2011, 2, 31, date(2011, 3, 3), date(2011, 2, 28), false},
+                {2011, 2, 32, date(2011, 3, 4), false, false},
+        };
+    }
+
+    @Test(dataProvider = "resolve_ymd")
+    public void test_resolve_ymd_lenient(int y, int m, int d, JapaneseDate expected, Object smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.MONTH_OF_YEAR, (long) m);
+        fieldValues.put(ChronoField.DAY_OF_MONTH, (long) d);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.LENIENT);
+        assertEquals(date, expected);
+        assertEquals(fieldValues.size(), 0);
+    }
+
+    @Test(dataProvider = "resolve_ymd")
+    public void test_resolve_ymd_smart(int y, int m, int d, JapaneseDate expected, Object smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.MONTH_OF_YEAR, (long) m);
+        fieldValues.put(ChronoField.DAY_OF_MONTH, (long) d);
+        if (Boolean.TRUE.equals(smart)) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
+            assertEquals(date, expected);
+            assertEquals(fieldValues.size(), 0);
+        } else if (smart instanceof JapaneseDate) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
+            assertEquals(date, smart);
+        } else {
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
+                fail("Should have failed");
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    @Test(dataProvider = "resolve_ymd")
+    public void test_resolve_ymd_strict(int y, int m, int d, JapaneseDate expected, Object smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.MONTH_OF_YEAR, (long) m);
+        fieldValues.put(ChronoField.DAY_OF_MONTH, (long) d);
+        if (strict) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
+            assertEquals(date, expected);
+            assertEquals(fieldValues.size(), 0);
+        } else {
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
+                fail("Should have failed");
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    @DataProvider(name = "resolve_yd")
+    Object[][] data_resolve_yd() {
+        return new Object[][] {
+                {2012, -365, date(2010, 12, 31), false, false},
+                {2012, -364, date(2011, 1, 1), false, false},
+                {2012, -31, date(2011, 11, 30), false, false},
+                {2012, -30, date(2011, 12, 1), false, false},
+                {2012, -12, date(2011, 12, 19), false, false},
+                {2012, -1, date(2011, 12, 30), false, false},
+                {2012, 0, date(2011, 12, 31), false, false},
+                {2012, 1, date(2012, 1, 1), true, true},
+                {2012, 2, date(2012, 1, 2), true, true},
+                {2012, 31, date(2012, 1, 31), true, true},
+                {2012, 32, date(2012, 2, 1), true, true},
+                {2012, 59, date(2012, 2, 28), true, true},
+                {2012, 60, date(2012, 2, 29), true, true},
+                {2012, 61, date(2012, 3, 1), true, true},
+                {2012, 365, date(2012, 12, 30), true, true},
+                {2012, 366, date(2012, 12, 31), true, true},
+                {2012, 367, date(2013, 1, 1), false, false},
+                {2012, 367 + 364, date(2013, 12, 31), false, false},
+                {2012, 367 + 365, date(2014, 1, 1), false, false},
+
+                {2011, 59, date(2011, 2, 28), true, true},
+                {2011, 60, date(2011, 3, 1), true, true},
+        };
+    }
+
+    @Test(dataProvider = "resolve_yd")
+    public void test_resolve_yd_lenient(int y, int d, JapaneseDate expected, boolean smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.DAY_OF_YEAR, (long) d);
+        JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.LENIENT);
+        assertEquals(date, expected);
+        assertEquals(fieldValues.size(), 0);
+    }
+
+    @Test(dataProvider = "resolve_yd")
+    public void test_resolve_yd_smart(int y, int d, JapaneseDate expected, boolean smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.DAY_OF_YEAR, (long) d);
+        if (smart) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
+            assertEquals(date, expected);
+            assertEquals(fieldValues.size(), 0);
+        } else {
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.SMART);
+                fail("Should have failed");
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    @Test(dataProvider = "resolve_yd")
+    public void test_resolve_yd_strict(int y, int d, JapaneseDate expected, boolean smart, boolean strict) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.YEAR, (long) y);
+        fieldValues.put(ChronoField.DAY_OF_YEAR, (long) d);
+        if (strict) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
+            assertEquals(date, expected);
+            assertEquals(fieldValues.size(), 0);
+        } else {
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, ResolverStyle.STRICT);
+                fail("Should have failed");
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    @DataProvider(name = "resolve_eymd")
+    Object[][] data_resolve_eymd() {
+        return new Object[][] {
+                // lenient
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 0, 1, 1, date(1988, 1, 1)},  // SHOWA, not HEISEI
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 1, 1, 1, date(1989, 1, 1)},  // SHOWA, not HEISEI
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 1, 1, 7, date(1989, 1, 7)},  // SHOWA, not HEISEI
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 1, 1, 8, date(1989, 1, 8)},
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 1, 12, 31, date(1989, 12, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.HEISEI, 2, 1, 1, date(1990, 1, 1)},
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 1, date(1989, 1, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 7, date(1989, 1, 7)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 8, date(1989, 1, 8)},  // HEISEI, not SHOWA
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 12, 31, date(1989, 12, 31)},  // HEISEI, not SHOWA
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 65, 1, 1, date(1990, 1, 1)},  // HEISEI, not SHOWA
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, -366, date(1987, 12, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, -365, date(1988, 1, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, -31, date(1988, 11, 30)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, -30, date(1988, 12, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 0, date(1988, 12, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 1, date(1989, 1, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 27, date(1989, 1, 27)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 28, date(1989, 1, 28)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 29, date(1989, 1, 29)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 30, date(1989, 1, 30)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 31, date(1989, 1, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 32, date(1989, 2, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 58, date(1989, 2, 27)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 59, date(1989, 2, 28)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 60, date(1989, 3, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 365, date(1989, 12, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 1, 366, date(1990, 1, 1)},
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 1, date(1988, 1, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 31, date(1988, 1, 31)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 32, date(1988, 2, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 58, date(1988, 2, 27)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 59, date(1988, 2, 28)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 60, date(1988, 2, 29)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 1, 61, date(1988, 3, 1)},
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 2, 1, date(1989, 2, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 2, 28, date(1989, 2, 28)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 64, 2, 29, date(1989, 3, 1)},
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 2, 1, date(1988, 2, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 2, 28, date(1988, 2, 28)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 2, 29, date(1988, 2, 29)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 63, 2, 30, date(1988, 3, 1)},
+
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 62, -11, 1, date(1986, 1, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 62, -1, 1, date(1986, 11, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 62, 0, 1, date(1986, 12, 1)},
+                {ResolverStyle.LENIENT, JapaneseEra.SHOWA, 62, 13, 1, date(1988, 1, 1)},
+
+                // smart
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 0, 1, 1, null},
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 1, 1, 1, date(1989, 1, 1)},  // SHOWA, not HEISEI
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 1, 1, 7, date(1989, 1, 7)},  // SHOWA, not HEISEI
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 1, 1, 8, date(1989, 1, 8)},
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 1, 12, 31, date(1989, 12, 31)},
+                {ResolverStyle.SMART, JapaneseEra.HEISEI, 2, 1, 1, date(1990, 1, 1)},
+
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 64, 1, 1, date(1989, 1, 1)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 64, 1, 7, date(1989, 1, 7)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 64, 1, 8, date(1989, 1, 8)},  // HEISEI, not SHOWA
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 64, 12, 31, date(1989, 12, 31)},  // HEISEI, not SHOWA
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 65, 1, 1, null},  // HEISEI, not SHOWA
+
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 0, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 1, date(1987, 1, 1)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 27, date(1987, 1, 27)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 28, date(1987, 1, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 29, date(1987, 1, 29)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 30, date(1987, 1, 30)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 31, date(1987, 1, 31)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 1, 32, null},
+
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 0, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 1, date(1987, 2, 1)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 27, date(1987, 2, 27)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 28, date(1987, 2, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 29, date(1987, 2, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 30, date(1987, 2, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 31, date(1987, 2, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 2, 32, null},
+
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 0, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 1, date(1988, 2, 1)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 27, date(1988, 2, 27)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 28, date(1988, 2, 28)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 29, date(1988, 2, 29)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 30, date(1988, 2, 29)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 31, date(1988, 2, 29)},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 63, 2, 32, null},
+
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, -12, 1, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, -1, 1, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 0, 1, null},
+                {ResolverStyle.SMART, JapaneseEra.SHOWA, 62, 13, 1, null},
+
+                // strict
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 0, 1, 1, null},
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 1, 1, 1, null},  // SHOWA, not HEISEI
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 1, 1, 7, null},  // SHOWA, not HEISEI
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 1, 1, 8, date(1989, 1, 8)},
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 1, 12, 31, date(1989, 12, 31)},
+                {ResolverStyle.STRICT, JapaneseEra.HEISEI, 2, 1, 1, date(1990, 1, 1)},
+
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 64, 1, 1, date(1989, 1, 1)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 64, 1, 7, date(1989, 1, 7)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 64, 1, 8, null},  // HEISEI, not SHOWA
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 64, 12, 31, null},  // HEISEI, not SHOWA
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 65, 1, 1, null},  // HEISEI, not SHOWA
+
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 0, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 1, date(1987, 1, 1)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 27, date(1987, 1, 27)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 28, date(1987, 1, 28)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 29, date(1987, 1, 29)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 30, date(1987, 1, 30)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 31, date(1987, 1, 31)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 1, 32, null},
+
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 0, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 1, date(1987, 2, 1)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 27, date(1987, 2, 27)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 28, date(1987, 2, 28)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 29, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 30, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 31, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 2, 32, null},
+
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 0, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 1, date(1988, 2, 1)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 27, date(1988, 2, 27)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 28, date(1988, 2, 28)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 29, date(1988, 2, 29)},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 30, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 31, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 63, 2, 32, null},
+
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, -12, 1, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, -1, 1, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 0, 1, null},
+                {ResolverStyle.STRICT, JapaneseEra.SHOWA, 62, 13, 1, null},
+        };
+    }
+
+    @Test(dataProvider = "resolve_eymd")
+    public void test_resolve_eymd(ResolverStyle style, JapaneseEra era, int yoe, int m, int d, JapaneseDate expected) {
+        Map<TemporalField, Long> fieldValues = new HashMap<>();
+        fieldValues.put(ChronoField.ERA, (long) era.getValue());
+        fieldValues.put(ChronoField.YEAR_OF_ERA, (long) yoe);
+        fieldValues.put(ChronoField.MONTH_OF_YEAR, (long) m);
+        fieldValues.put(ChronoField.DAY_OF_MONTH, (long) d);
+        if (expected != null) {
+            JapaneseDate date = JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+            assertEquals(date, expected);
+            assertEquals(fieldValues.size(), 0);
+        } else {
+            try {
+                JapaneseChronology.INSTANCE.resolveDate(fieldValues, style);
+                fail("Should have failed");
+            } catch (DateTimeException ex) {
+                // expected
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    private static JapaneseDate date(int y, int m, int d) {
+        return JapaneseDate.of(y, m, d);
+    }
+
 }
RogerRiggs commented 11 years ago

looks fine, thanks

jodastephen commented 11 years ago

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

jodastephen commented 11 years ago

Additional tests for date creation: http://hg.openjdk.java.net/threeten/threeten/jdk/rev/99ceed2a0265

jodastephen commented 11 years ago

Tweak http://hg.openjdk.java.net/threeten/threeten/jdk/rev/cfba4fe9fdc0

jodastephen commented 11 years ago

Just need Masoyoshi/Sherman/I18N team to confirm behaviour outlined in this comment

RogerRiggs commented 10 years ago

The implementation and strategy has been in place for 3 months and no issues have been raised. Closing.