Closed jodastephen closed 10 years ago
Since #272 is closed, does the javadoc describing DAY_OF_YEAR need to be updated?
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.
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."
Please raise a new issue so this doesn't get forgotten.
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?
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.
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
# 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);
+ }
+
}
looks fine, thanks
Additional tests for date creation: http://hg.openjdk.java.net/threeten/threeten/jdk/rev/99ceed2a0265
Just need Masoyoshi/Sherman/I18N team to confirm behaviour outlined in this comment
The implementation and strategy has been in place for 3 months and no issues have been raised. Closing.
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.