Skip to content

Fix implementation of resolving date in Japanese chronology #299

Closed
jodastephen opened this Issue Apr 1, 2013 · 14 comments

2 participants

@jodastephen
ThreeTen member

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

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

@jodastephen
ThreeTen member

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

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
ThreeTen member

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

@RogerRiggs

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
ThreeTen member

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
ThreeTen member

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

  • Validate the era fully in all modes
  • In lenient mode, any year-of-era is allowed for an era. Thus SHOWA 69 is allowed and resolves to 5 years after SHOWA 64.
  • In strict mode, all four parts of the date must be valid, equal to the method JapaneseChronology.date(era, yoe, month, dom). Thus SHOWA 64-01-08 will fail to parse.
  • In smart mode, SHOWA 64 and HEISEI 1 are both valid ways to refer to all dates in the year 1989. However SHOWA 65 would fail to parse, as would HEISEI 0. Thus, the smart flexibility only extends to the year when the era changes, and not other years.
@jodastephen
ThreeTen member
# 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);
+    }
+
 }
@jodastephen jodastephen was assigned Jun 24, 2013
@RogerRiggs

looks fine, thanks

@jodastephen
ThreeTen member
@jodastephen
ThreeTen member

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

@RogerRiggs RogerRiggs was assigned Sep 12, 2013
@RogerRiggs

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

@RogerRiggs RogerRiggs closed this Oct 10, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.