/*
|
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 {
|
|
public static final long STARTTAG = new ISOKalender().zuTagen(-2636, Definition.FEBRUAR, 15);
|
|
private long zyklus;
|
private boolean schaltmonat;
|
|
public void setZyklus(long zyklus) {
|
this.zyklus = zyklus;
|
}
|
|
public void setSchaltmonat(boolean schaltmonat) {
|
this.schaltmonat = schaltmonat;
|
}
|
|
public long zuTagen(long zyklus, int jahr, int monat, boolean schaltmonat, int tag) {
|
this.zyklus = zyklus;
|
this.schaltmonat = schaltmonat;
|
return zuTagen(jahr, monat, tag);
|
}
|
|
@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;
|
}
|
|
@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());
|
}
|
}
|
|
@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 boolean hatSchaltmonatVorher(long mPrime, long m) {
|
return m >= mPrime && (keinSolarerHauptabschnitt(m) || hatSchaltmonatVorher(mPrime, chinesischerNeumondVor(m)));
|
}
|
|
public double mitternachtInChina(long date) {
|
return universalVonStandard(date, chinesischerOrt(date));
|
}
|
|
public final Ort peking(double tee) {
|
ISOKalender g = new ISOKalender();
|
long year = g.jahrVonTagen((long)Math.floor(tee));
|
return new Ort("Peking", (double) (39.55), winkel(116,25,0),
|
(double) (43.5), year < 1929 ? 1397d/180 : 8);
|
}
|
|
public final Ort chinesischerOrt(double tee) {
|
return peking(tee);
|
}
|
|
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 nextM11 = chinesischerNeumondVor(s2 + 1);
|
if((Math.round((nextM11 - m12) / MITTLERER_SYNODISCHER_MONAT) == 12) && (keinSolarerHauptabschnitt(m12) || keinSolarerHauptabschnitt(m13))) {
|
return chinesischerNeumondAmOderNach(m13 + 1);
|
} else {
|
return m13;
|
}
|
}
|
|
public long neujahr(long gYear) {
|
ISOKalender g = new ISOKalender();
|
return neujahrAmOderVor(g.zuTagen(gYear, Definition.JULI, 1));
|
}
|
|
public long qingMing(long gYear) {
|
ISOKalender g = new ISOKalender();
|
return (long)Math.floor(solarerNebenabschnittAmOderNach(g.zuTagen(gYear, Definition.MAERZ, 30)));
|
}
|
|
public long cDatumZuGenerisch(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);
|
}
|
|
public long winterSonnenwendeAmOderVor(long date) {
|
double approx = geschaetzteSolareLaengeVor(mitternachtInChina(date + 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 date) {
|
return aktuellerSolarerHauptabschnitt(date) ==
|
aktuellerSolarerHauptabschnitt(chinesischerNeumondAmOderNach(date + 1));
|
}
|
|
public int aktuellerSolarerHauptabschnitt(long date) {
|
double s = solareLaenge(standardVonUniversal(date, chinesischerOrt(date)));
|
return (int)moduloAngepasst(2 + ganzzahlQuotient(s, (double) (30)), 12);
|
}
|
|
public double solarerHauptabschnittAmOderNach(long date) {
|
double l = modulo(30 * Math.ceil(solareLaenge(mitternachtInChina(date)) / 30), 360);
|
return chinesischeSolareLaengeAmOderNach(date, l);
|
}
|
|
public double solarerNebenabschnittAmOderNach(long date) {
|
double l = modulo(30 * Math.ceil((solareLaenge(mitternachtInChina(date)) - (double) (15)) / 30) + (double) (15), 360);
|
return chinesischeSolareLaengeAmOderNach(date, l);
|
}
|
|
public double chinesischeSolareLaengeAmOderNach(long date, double theta) {
|
Ort beijing = chinesischerOrt(date);
|
double tee = solareLaengeNach(standardVonUniversal(date, beijing), theta);
|
return standardVonUniversal(tee, beijing);
|
}
|
|
public int aktuellerSolarerUnterabschnitt(long date) {
|
double s = solareLaenge(mitternachtInChina(date));
|
return (int)moduloAngepasst(3 + ganzzahlQuotient(s - (double) (15), (double) (30)), 12);
|
}
|
|
public long chinesischerNeumondAmOderNach(long date) {
|
double t = neumondNach(mitternachtInChina(date));
|
return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
|
}
|
|
public long chinesischerNeumondVor(long date) {
|
double t = neumondVor((long) mitternachtInChina(date));
|
return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
|
}
|
}
|