/*
|
Zeitrechnung - a class library to determine calendar events
|
Copyright (c) 1984-2023 Ulrich Hilger, http://uhilger.de
|
|
This program is free software: you can redistribute it and/or modify
|
it under the terms of the GNU Affero General Public License as published by
|
the Free Software Foundation, either version 3 of the License, or
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
GNU Affero General Public License for more details.
|
|
You should have received a copy of the GNU Affero General Public License
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
*/
|
package de.uhilger.zeitrechnung.kalender;
|
|
import de.uhilger.zeitrechnung.ChinesischesDatum;
|
import de.uhilger.zeitrechnung.Datum;
|
import de.uhilger.zeitrechnung.Definition;
|
import de.uhilger.zeitrechnung.Ort;
|
|
/**
|
* Der traditionelle chinesichen Kalender ist unterteilt in Mond- und Sonnenjahr.
|
* Das Sonnenjahr (sui) beginnt am Tag der Wintersonnenwende ist weiter unterteilt
|
* in solare Abschnitte und noch weiter unterteilt in solare Haupt- und Unterabschnitte.
|
*
|
* Die Bahn, die die Sonne innerhalb eines tropischen Jahres von 365,24 Tagen scheinbar
|
* auf der Ekliptik durchläuft, wird in 24 Teile von je 15 Grad unterteilt. Dies sind die 24
|
* Stationen oder Jahreseinteilungen (節氣 / 节气, jiéqì). Jede zweite Station ist ein
|
* Zhongqi (中氣 / 中气, zhōngqì – „zentrale/Haupt-Jahreseinteilung“), wobei die Sonnenwenden
|
* und Tagundnachtgleichen vier der zwölf Zhongqi sind. Der zeitliche Abstand von einem
|
* Zhongqi zum nächsten beträgt im Mittel ein Zwölftel eines tropischen Jahres oder 30,44 Tage.
|
* Er variiert leicht aufgrund der elliptischen Umlaufbahn der Erde um die Sonne.
|
*
|
* Jeweils sechs Jahreseinteilungen gehören zu einer Jahreszeit. Während aber im westlichen
|
* Kalender die Jahreszeiten mit dem Tag der Sonnenwende bzw. der Tagundnachtgleiche beginnen,
|
* liegen im chinesischen Kalender diese Tage in der Mitte der jeweiligen Jahreszeit. Man zählt
|
* die Stationen beginnend mit dem Frühlingsanfang – lichun, und mancherorts gilt der
|
* Frühlingsanfang (und nicht die Wintersonnenwende) als Beginn des sui-Jahres.
|
*
|
* Eine Besonderheit des Chinesischen Kalenders ist, dass ein Chinesisches Datum neben
|
* Tag, Monat und Jahr auch den Zyklus und die Angabe zum Schaltmonat benoetigt. Um
|
* die Schnittstelle Wandler vollstaendig implementieren zu koennen, werden
|
* die Methoden setZyklus und setSchaltmonat zusaetzlich eingebaut.
|
*
|
* Bei Nutzung der Wandler-Schnittstelle sollte im Falle des Chinesischen Kalenders
|
* darauf geachtet werden, dass der Methode zuTagen() entweder ein Datum der
|
* Klasse ChinesischesDatum uebergeben wird oder zuvor setZyklus und setSchaltmonat genutzt wird.
|
* Auch vor Nutzung der Methode zuTagen(jahr, monat, tag) muss zuvor setZyklus und
|
* setSchaltmonat genutzt oder stattdessen die Methode
|
* zuTagen(zyklus, jahr, monat, schaltmonat, tag) verwendet werden.
|
*
|
* @author Ulrich Hilger
|
*/
|
public class ChinesischerKalender extends BasisKalender implements Wandler {
|
|
/**
|
* Der Start der chinesischen Zeitrechung laesst sich aus dem heutigen
|
* chinesischen Datum errechnen. Das erste Jahr des ersten Zyklus faellt
|
* so auf den 15. Februar -2636 im gregorianischen Kalender.
|
*/
|
public static final long STARTTAG = new ISOKalender().zuTagen(-2636, Definition.FEBRUAR, 15);
|
|
private long zyklus;
|
private boolean schaltmonat;
|
|
/**
|
* Den Zyklus angeben, in dem ein Jahr des chinesischen Kalenders liegt, das
|
* mit der Methode zuTagen(jahr, monat, tag) in ein generisches Datum
|
* umgerechnet werden soll.
|
*
|
* Diese Methode ist eine Hilfsfunktion, wenn nicht die Methode
|
* zuTagen(zyklus, jahr, monat, schaltmonat, tag) genutzt werden kann.
|
*
|
* @param zyklus der Zyklus im chinesischen Ḱalender
|
*/
|
public void setZyklus(long zyklus) {
|
this.zyklus = zyklus;
|
}
|
|
/**
|
* Angeben, ob ein Monat eines Datums im chinesischen Kalender ein
|
* Schaltmonat ist, das mit der Methode
|
* zuTagen(jahr, monat, tag) in ein generisches Datum
|
* umgerechnet werden soll.
|
*
|
* Diese Methode ist eine Hilfsfunktion, wenn nicht die Methode
|
* zuTagen(zyklus, jahr, monat, schaltmonat, tag) genutzt werden kann.
|
*
|
* @param schaltmonat
|
*/
|
public void setSchaltmonat(boolean schaltmonat) {
|
this.schaltmonat = schaltmonat;
|
}
|
|
/**
|
* Die Anzahl der Tage ermitteln, die zwischen einem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
*
|
* @param zyklus der Zyklus des chinesischen Kalenders
|
* @param jahr das Jahr des chinesischen Kalenders
|
* @param monat der Monat des chinesischen Kalenders
|
* @param schaltmonat true, wenn der Monat ein Schaltmonat ist, sonst false
|
* @param tag Tag im Monat des chinesischen Kalenders
|
*
|
* @return Anzahl Tage, die zwischen dem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
* Liegt das gegebene Datum vor dem 1. Januar 1 (gregorianisch), wird
|
* eine negative Zahl zurueckgegeben.
|
*/
|
public long zuTagen(long zyklus, int jahr, int monat, boolean schaltmonat, int tag) {
|
this.zyklus = zyklus;
|
this.schaltmonat = schaltmonat;
|
return zuTagen(jahr, monat, tag);
|
}
|
|
/**
|
* Die Anzahl der Tage ermitteln, die zwischen einem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
*
|
* Bei Verwendung dieser Methode muessen zuvor die Methode setZyklus
|
* und setSchaltmonat genutzt werden.
|
*
|
* @param jahr das Jahr im betreffenden Kalendersystem
|
* @param monat der Monat im betreffenden Kalendersystem
|
* @param tag der Tag im betreffenden Kalendersysem
|
*
|
* @return Anzahl Tage, die zwischen dem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
* Liegt das gegebene Datum vor dem 1. Januar 1 (gregorianisch), wird
|
* eine negative Zahl zurueckgegeben.
|
*/
|
@Override
|
public long zuTagen(long jahr, int monat, int tag) {
|
long midYear = (long)Math.floor(STARTTAG + ((zyklus - 1) * 60 + (jahr - 1) + .5) * MITTLERES_TROPISCHES_JAHR);
|
long theNewYear = neujahrAmOderVor(midYear);
|
long p = chinesischerNeumondAmOderNach(theNewYear + 29 * (monat - 1));
|
ChinesischesDatum d = vonTagen(p);
|
long priorNewMoon = monat == d.getMonat() && schaltmonat == d.isSchaltmonat() ?
|
p :
|
chinesischerNeumondAmOderNach(p + 1);
|
return priorNewMoon + tag - 1;
|
}
|
|
/**
|
* Die Anzahl der Tage ermitteln, die zwischen einem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
*
|
* @param d das Datum im betreffenden Kalendersystem
|
*
|
* @return Anzahl Tage, die zwischen dem gegebenen Datum
|
* des chinesischen Kalenders und dem Tag liegen, der im
|
* Gregorianischen Kalender mit dem Datum 1. Januar 1 bezeichnet ist.
|
* Liegt das gegebene Datum vor dem 1. Januar 1 (gregorianisch), wird
|
* eine negative Zahl zurueckgegeben.
|
*/
|
@Override
|
public long zuTagen(Datum d) {
|
if(d instanceof ChinesischesDatum) {
|
ChinesischesDatum cd = (ChinesischesDatum) d;
|
return zuTagen(cd.getZyklus(), (int) cd.getJahr(), cd.getMonat(), cd.isSchaltmonat(), cd.getTag());
|
} else {
|
return zuTagen(zyklus, (int) d.getJahr(), d.getMonat(), schaltmonat, d.getTag());
|
}
|
}
|
|
/**
|
* Das Datum des chinesischen Kalenders fuer ein generisches Datum
|
* ermitteln.
|
*
|
* @param tage Anzahl der Tage zwischen 1. Januar 1 im gregorianischen
|
* Kalender und dem Tag, dessen Datum des chinesischen Kalenders
|
* ermittelt werden soll
|
* @return das Datum im chinesischen Kalender
|
*/
|
@Override
|
public ChinesischesDatum vonTagen(long tage) {
|
long s1 = winterSonnenwendeAmOderVor(tage);
|
long s2 = winterSonnenwendeAmOderVor(s1 + 370);
|
long m12 = chinesischerNeumondAmOderNach(s1 + 1);
|
long nextM11 = chinesischerNeumondVor(s2 + 1);
|
long m = chinesischerNeumondVor(tage + 1);
|
boolean leapYear = Math.round((nextM11 - m12) / MITTLERER_SYNODISCHER_MONAT) == 12;
|
int month = (int)moduloAngepasst(
|
(long)Math.round((m - m12) / MITTLERER_SYNODISCHER_MONAT) -
|
(leapYear && hatSchaltmonatVorher(m12, m) ? 1 : 0),
|
12);
|
boolean leapMonth = leapYear &&
|
keinSolarerHauptabschnitt(m) &&
|
!hatSchaltmonatVorher(m12, chinesischerNeumondVor(m));
|
long elapsedYears = (long)Math.floor(1.5 - (month / 12d) + (tage - STARTTAG) / MITTLERES_TROPISCHES_JAHR);
|
long cycle = ganzzahlQuotient(elapsedYears - 1, 60) + 1;
|
int year = (int)moduloAngepasst(elapsedYears, 60);
|
int day = (int)(tage - m + 1);
|
return new ChinesischesDatum(cycle, year, month, leapMonth, day);
|
}
|
|
public long neujahr(long isoJahr) {
|
ISOKalender g = new ISOKalender();
|
return neujahrAmOderVor(g.zuTagen(isoJahr, Definition.JULI, 1));
|
}
|
|
public long qingMing(long isoJahr) {
|
ISOKalender g = new ISOKalender();
|
return (long)Math.floor(solarerNebenabschnittAmOderNach(g.zuTagen(isoJahr, Definition.MAERZ, 30)));
|
}
|
|
/**
|
* Das generische Datum des Tages mit einem gegebenen festen Datum
|
* im chinesischen Kalender fuer ein gegebenes gregorianisches Jahr
|
* ermitteln
|
*
|
* @param isoJahr Jahr im gregorischen Kalender, in das ein gesuchtes Ereignis faellt
|
* @param cMonat Monat im chinesischen Kalender, an dem das Ereignis stattfindet
|
* @param cTag Tag im Monat des chinesischen Kalenders, an dem das Ereignis stattfindet
|
* @return generisches Datum des Ereignisses
|
*/
|
public long cDatumZuTagen(long isoJahr, int cMonat, int cTag) {
|
ISOKalender g = new ISOKalender();
|
long elapsedYears = isoJahr - g.jahrVonTagen(STARTTAG) + 1;
|
long cycle = ganzzahlQuotient(elapsedYears - 1, 60) + 1;
|
int year = (int)moduloAngepasst(elapsedYears, 60);
|
return zuTagen(cycle, year, cMonat, false, cTag);
|
}
|
|
/* ---- Hilfsfunktionen --------- */
|
|
public boolean hatSchaltmonatVorher(long mHaupt, long m) {
|
return m >= mHaupt && (keinSolarerHauptabschnitt(m) || hatSchaltmonatVorher(mHaupt, chinesischerNeumondVor(m)));
|
}
|
|
public double mitternachtInChina(long tage) {
|
return universalVonStandard(tage, chinesischerOrt(tage));
|
}
|
|
public final Ort peking(double t) {
|
ISOKalender g = new ISOKalender();
|
long year = g.jahrVonTagen((long)Math.floor(t));
|
return new Ort("Peking", (double) (39.55), winkel(116,25,0),
|
(double) (43.5), year < 1929 ? 1397d/180 : 8);
|
}
|
|
public final Ort chinesischerOrt(double t) {
|
return peking(t);
|
}
|
|
public long neujahrAmOderVor(long tage) {
|
long neujahr = neujahrInSui(tage);
|
return tage >= neujahr ? neujahr : neujahrInSui(tage - 180);
|
}
|
|
public long neujahrInSui(long tage) {
|
long s1 = winterSonnenwendeAmOderVor(tage);
|
long s2 = winterSonnenwendeAmOderVor(s1 + 370);
|
long m12 = chinesischerNeumondAmOderNach(s1 + 1);
|
long m13 = chinesischerNeumondAmOderNach(m12 + 1);
|
long naechsterM11 = chinesischerNeumondVor(s2 + 1);
|
if((Math.round((naechsterM11 - m12) / MITTLERER_SYNODISCHER_MONAT) == 12) && (keinSolarerHauptabschnitt(m12) || keinSolarerHauptabschnitt(m13))) {
|
return chinesischerNeumondAmOderNach(m13 + 1);
|
} else {
|
return m13;
|
}
|
}
|
|
public long winterSonnenwendeAmOderVor(long tage) {
|
double approx = geschaetzteSolareLaengeVor(mitternachtInChina(tage + 1), Definition.WINTER);
|
long i;
|
for(i = (long)(Math.floor(approx) - 1); !(Definition.WINTER <= solareLaenge(mitternachtInChina(i + 1))); ++i);
|
return i;
|
}
|
|
public boolean keinSolarerHauptabschnitt(long tage) {
|
return aktuellerSolarerHauptabschnitt(tage) ==
|
aktuellerSolarerHauptabschnitt(chinesischerNeumondAmOderNach(tage + 1));
|
}
|
|
public int aktuellerSolarerHauptabschnitt(long tage) {
|
double s = solareLaenge(standardVonUniversal(tage, chinesischerOrt(tage)));
|
return (int)moduloAngepasst(2 + ganzzahlQuotient(s, (double) (30)), 12);
|
}
|
|
public double solarerHauptabschnittAmOderNach(long tage) {
|
double l = modulo(30 * Math.ceil(solareLaenge(mitternachtInChina(tage)) / 30), 360);
|
return chinesischeSolareLaengeAmOderNach(tage, l);
|
}
|
|
public double solarerNebenabschnittAmOderNach(long tage) {
|
double l = modulo(30 * Math.ceil((solareLaenge(mitternachtInChina(tage)) - (double) (15)) / 30) + (double) (15), 360);
|
return chinesischeSolareLaengeAmOderNach(tage, l);
|
}
|
|
public double chinesischeSolareLaengeAmOderNach(long tage, double theta) {
|
Ort peking = chinesischerOrt(tage);
|
double t = solareLaengeNach(standardVonUniversal(tage, peking), theta);
|
return standardVonUniversal(t, peking);
|
}
|
|
public int aktuellerSolarerUnterabschnitt(long tage) {
|
double s = solareLaenge(mitternachtInChina(tage));
|
return (int)moduloAngepasst(3 + ganzzahlQuotient(s - (double) (15), (double) (30)), 12);
|
}
|
|
public long chinesischerNeumondAmOderNach(long tage) {
|
double t = neumondNach(mitternachtInChina(tage));
|
return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
|
}
|
|
public long chinesischerNeumondVor(long tage) {
|
double t = neumondVor((long) mitternachtInChina(tage));
|
return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
|
}
|
}
|