Skip to content

Commit

Permalink
improved cyclic year handling
Browse files Browse the repository at this point in the history
see issue #638
  • Loading branch information
MenoData committed Feb 6, 2018
1 parent 1ed9418 commit 027740f
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 15 deletions.
44 changes: 35 additions & 9 deletions calendar/src/main/java/net/time4j/calendar/CyclicYear.java
Expand Up @@ -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 ------------------------------------------------------

Expand Down Expand Up @@ -384,6 +385,9 @@ public CyclicYear roll(int amount) {
/**
* <p>Obtains an unambivalent year reference for given Qing dynasty. </p>
*
* <p>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. </p>
*
* @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
Expand All @@ -392,6 +396,9 @@ public CyclicYear roll(int amount) {
/*[deutsch]
* <p>Liefert eine eindeutige Jahresreferenz zur angegebenen Qing-Dynastie. </p>
*
* <p>Hinweis: Die Jahre AD 1662 und AD 1722 haben das gleiche zyklische Jahr, so da&szlig; eine eindeutige
* Jahresreferenz unm&ouml;glich ist. Das ist durch die L&auml;nge der Kangxi-&Auml;ra von 61 Jahren bedingt. </p>
*
* @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
Expand All @@ -402,21 +409,40 @@ 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.");
}

}

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());
/**
* <p>Obtains a year reference for given cycle number (technical identifier). </p>
*
* @param cycle number of sexagesimal year cycle
* @return EastAsianYear
* @throws IllegalArgumentException if the cycle is smaller than {@code 1}
*/
/*[deutsch]
* <p>Liefert eine Jahresreferenz zur angegebenen Jahreszyklusnummer (technisches Kennzeichen). </p>
*
* @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
Expand Down Expand Up @@ -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) + ")";

}

Expand Down
11 changes: 5 additions & 6 deletions calendar/src/main/java/net/time4j/calendar/EastAsianYear.java
Expand Up @@ -29,12 +29,16 @@
*
* @author Meno Hochschild
* @since 3.40/4.35
* @see CyclicYear#inQingDynasty(ChineseEra)
* @see CyclicYear#inCycle(int)
*/
/*[deutsch]
* <p>Repr&auml;sentiert einen Weg, das Jahr im chinesischen Kalender oder dessen Ableitungen anzugeben. </p>
*
* @author Meno Hochschild
* @since 3.40/4.35
* @see CyclicYear#inQingDynasty(ChineseEra)
* @see CyclicYear#inCycle(int)
*/
@FunctionalInterface
public interface EastAsianYear {
Expand All @@ -46,21 +50,16 @@ 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]
* <p>Bestimmt das ostasiatische Jahr, das dem angegebenen gregorianischen Kalenderjahr entspricht, so da&szlig;
* das gregorianische Jahr den Neujahrstag des ostasiatischen Jahres enth&auml;lt. </p>
*
* @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);
}

/**
Expand Down
70 changes: 70 additions & 0 deletions 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)));
}

}

0 comments on commit 027740f

Please sign in to comment.