From 027740f698e99afe29a5d2776e3b1547a9de70c2 Mon Sep 17 00:00:00 2001 From: Meno Hochschild Date: Tue, 6 Feb 2018 05:33:39 +0100 Subject: [PATCH] improved cyclic year handling see issue #638 --- .../java/net/time4j/calendar/CyclicYear.java | 44 +++++++++--- .../net/time4j/calendar/EastAsianYear.java | 11 ++- .../net/time4j/calendar/ChineseYearTest.java | 70 +++++++++++++++++++ 3 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 calendar/src/test/java/net/time4j/calendar/ChineseYearTest.java diff --git a/calendar/src/main/java/net/time4j/calendar/CyclicYear.java b/calendar/src/main/java/net/time4j/calendar/CyclicYear.java index 39cdd838c..2d0adedf5 100644 --- a/calendar/src/main/java/net/time4j/calendar/CyclicYear.java +++ b/calendar/src/main/java/net/time4j/calendar/CyclicYear.java @@ -132,7 +132,8 @@ public final class CyclicYear LANGS_WITHOUT_SEP = Collections.unmodifiableSet(set); } - private static final long serialVersionUID = 6677607109702604931L; + // TODO: evaluate again + // private static final long serialVersionUID = 6677607109702604931L; //~ Instanzvariablen ------------------------------------------------------ @@ -384,6 +385,9 @@ public CyclicYear roll(int amount) { /** *

Obtains an unambivalent year reference for given Qing dynasty.

* + *

Note: The years AD 1662 and AD 1722 have the same cyclic year so an unambivalent year reference + * cannot be determined. This is because the Kangxi-era was 61 years long.

+ * * @param era the Chinese era representing a historic Qing dynasty * @return EastAsianYear * @throws IllegalArgumentException if the era is not a Qing dynasty or if the combination of this cyclic year @@ -392,6 +396,9 @@ public CyclicYear roll(int amount) { /*[deutsch] *

Liefert eine eindeutige Jahresreferenz zur angegebenen Qing-Dynastie.

* + *

Hinweis: Die Jahre AD 1662 und AD 1722 haben das gleiche zyklische Jahr, so daß eine eindeutige + * Jahresreferenz unmöglich ist. Das ist durch die Länge der Kangxi-Ära von 61 Jahren bedingt.

+ * * @param era the Chinese era representing a historic Qing dynasty * @return EastAsianYear * @throws IllegalArgumentException if the era is not a Qing dynasty or if the combination of this cyclic year @@ -402,10 +409,11 @@ public EastAsianYear inQingDynasty(ChineseEra era) { if (era.isQingDynasty()) { if ((era == ChineseEra.QING_KANGXI_1662_1723) && (this.year == 39)){ throw new IllegalArgumentException( - "Ambivalent cyclic year in Kangxi-era: " + this.getDisplayName(Locale.ROOT)); + "Ambivalent cyclic year in Kangxi-era (1662 or 1722): " + this.getDisplayName(Locale.ROOT)); } else { - int relgregyear = era.getStartAsGregorianYear() + this.year - 1; - return () -> relgregyear + 2636; // TODO: falsch (berichtigen) + int start = era.getStartAsGregorianYear(); + int delta = this.year - EastAsianYear.forGregorian(start).getYearOfCycle().getNumber(); + return () -> start + delta + ((delta < 0) ? 2696 : 2636); } } else { throw new IllegalArgumentException("Chinese era must be related to a Qing dynasty."); @@ -413,10 +421,28 @@ public EastAsianYear inQingDynasty(ChineseEra era) { } - public static void main(String[] args) { - System.out.println(EastAsianYear.forGregorian(1662).getYearOfCycle()); - System.out.println(EastAsianYear.forGregorian(1722).getYearOfCycle()); - System.out.println(CyclicYear.of(40).inQingDynasty(ChineseEra.QING_KANGXI_1662_1723).getYearOfCycle()); + /** + *

Obtains a year reference for given cycle number (technical identifier).

+ * + * @param cycle number of sexagesimal year cycle + * @return EastAsianYear + * @throws IllegalArgumentException if the cycle is smaller than {@code 1} + */ + /*[deutsch] + *

Liefert eine Jahresreferenz zur angegebenen Jahreszyklusnummer (technisches Kennzeichen).

+ * + * @param cycle number of sexagesimal year cycle + * @return EastAsianYear + * @throws IllegalArgumentException if the cycle is smaller than {@code 1} + */ + public EastAsianYear inCycle(int cycle) { + + if (cycle < 1) { + throw new IllegalArgumentException("Cycle number must not be smaller than 1: " + cycle); + } + + return () -> (cycle - 1) * 60 + this.year - 1; + } @Override @@ -447,7 +473,7 @@ public int hashCode() { @Override public String toString() { - return String.valueOf(this.year); + return this.getDisplayName(Locale.ROOT) + "(" + String.valueOf(this.year) + ")"; } diff --git a/calendar/src/main/java/net/time4j/calendar/EastAsianYear.java b/calendar/src/main/java/net/time4j/calendar/EastAsianYear.java index c4fb3c388..f51f2e8b8 100644 --- a/calendar/src/main/java/net/time4j/calendar/EastAsianYear.java +++ b/calendar/src/main/java/net/time4j/calendar/EastAsianYear.java @@ -29,12 +29,16 @@ * * @author Meno Hochschild * @since 3.40/4.35 + * @see CyclicYear#inQingDynasty(ChineseEra) + * @see CyclicYear#inCycle(int) */ /*[deutsch] *

Repräsentiert einen Weg, das Jahr im chinesischen Kalender oder dessen Ableitungen anzugeben.

* * @author Meno Hochschild * @since 3.40/4.35 + * @see CyclicYear#inQingDynasty(ChineseEra) + * @see CyclicYear#inCycle(int) */ @FunctionalInterface public interface EastAsianYear { @@ -46,7 +50,6 @@ public interface EastAsianYear { * * @param relatedGregorianYear the gregorian calendar year which contains the first day of East Asian year * @return EastAsianYear - * @throws IllegalArgumentException if the year is out of range of Chinese calendar */ /*[deutsch] *

Bestimmt das ostasiatische Jahr, das dem angegebenen gregorianischen Kalenderjahr entspricht, so daß @@ -54,13 +57,9 @@ public interface EastAsianYear { * * @param relatedGregorianYear the gregorian calendar year which contains the first day of East Asian year * @return EastAsianYear - * @throws IllegalArgumentException if the year is out of range of Chinese calendar */ static EastAsianYear forGregorian(int relatedGregorianYear) { - if (relatedGregorianYear < 1645 || relatedGregorianYear > 2999) { - throw new IllegalArgumentException("Related gregorian year out of range: " + relatedGregorianYear); - } - return () -> relatedGregorianYear + 2636; + return () -> MathUtils.safeAdd(relatedGregorianYear, 2636); } /** diff --git a/calendar/src/test/java/net/time4j/calendar/ChineseYearTest.java b/calendar/src/test/java/net/time4j/calendar/ChineseYearTest.java new file mode 100644 index 000000000..5fbcca8f0 --- /dev/null +++ b/calendar/src/test/java/net/time4j/calendar/ChineseYearTest.java @@ -0,0 +1,70 @@ +package net.time4j.calendar; + +import net.time4j.format.TextWidth; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Locale; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + + +@RunWith(JUnit4.class) +public class ChineseYearTest { + + @Test + public void forGregorian2637BC() { + EastAsianYear eay = EastAsianYear.forGregorian(-2636); + assertThat(eay.getCycle(), is(1)); + assertThat(eay.getYearOfCycle().getNumber(), is(1)); + assertThat(eay.getElapsedCyclicYears(), is(0)); + CyclicYear cy = eay.getYearOfCycle(); + assertThat(cy.inCycle(eay.getCycle()).getElapsedCyclicYears(), is(eay.getElapsedCyclicYears())); + } + + @Test + public void forGregorian1998() { + EastAsianYear eay = EastAsianYear.forGregorian(1998); + assertThat(eay.getCycle(), is(78)); + assertThat(eay.getYearOfCycle().getNumber(), is(15)); + assertThat(eay.getElapsedCyclicYears(), is(4634)); + CyclicYear cy = eay.getYearOfCycle(); + assertThat(cy.inCycle(eay.getCycle()).getElapsedCyclicYears(), is(eay.getElapsedCyclicYears())); + } + + @Test + public void forMinguo() { + EastAsianYear eay = EastAsianYear.forMinguo(2); + assertThat(eay.getCycle(), is(76)); + assertThat(eay.getYearOfCycle().getNumber(), is(50)); + assertThat(eay.getElapsedCyclicYears(), is(4634 - 85)); + CyclicYear cy = eay.getYearOfCycle(); + assertThat(cy.inCycle(eay.getCycle()).getElapsedCyclicYears(), is(eay.getElapsedCyclicYears())); + } + + @Test(expected=IllegalArgumentException.class) + public void inQingDynastyAmbivalent() { + CyclicYear.of(39).inQingDynasty(ChineseEra.QING_KANGXI_1662_1723); + } + + @Test + public void inQingDynasty() { + for (int i = 1; i <= 60; i++) { + if (i != 39) { + assertThat( + CyclicYear.of(i).inQingDynasty(ChineseEra.QING_KANGXI_1662_1723).getYearOfCycle().getNumber(), + is(i)); + } + } + } + + @Test + public void roll() { + assertThat(CyclicYear.of(5).roll(-7), is(CyclicYear.of(58))); + assertThat(CyclicYear.of(5).roll(59), is(CyclicYear.of(4))); + assertThat(CyclicYear.of(5).roll(61), is(CyclicYear.of(6))); + } + +} \ No newline at end of file