Bestimmung der Zeitpunkte von Ereignissen
ulrich
2023-03-23 d20d989f5495492f1258c8313db7c19b429111a3
Chinesischer Kalender implementiert
3 files added
5 files modified
507 ■■■■■ changed files
src/de/uhilger/zeitrechnung/ChinesischesDatum.java 37 ●●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ereignis/ChinesischesEreignis.java 100 ●●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ereignis/EinzelEreignis.java 20 ●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ereignis/Ereignis.java 3 ●●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ereignis/EreignisBasis.java 2 ●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ereignis/JulianischesEreignis.java 3 ●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/kalender/BasisKalender.java 115 ●●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/kalender/ChinesischerKalender.java 227 ●●●●● patch | view | raw | blame | history
src/de/uhilger/zeitrechnung/ChinesischesDatum.java
New file
@@ -0,0 +1,37 @@
package de.uhilger.zeitrechnung;
/**
 *
 * @author Ulrich Hilger
 */
public class ChinesischesDatum extends Datum {
  private long zyklus;
  private boolean schaltmonat;
  public ChinesischesDatum() {
    super();
  }
  public ChinesischesDatum(long zyklus, long jahr, int monat, boolean schaltmonat, int tag) {
    super(jahr, monat, tag);
    this.zyklus = zyklus;
    this.schaltmonat = schaltmonat;
  }
  public long getZyklus() {
    return zyklus;
  }
  public void setZyklus(long zyklus) {
    this.zyklus = zyklus;
  }
  public boolean isSchaltmonat() {
    return schaltmonat;
  }
  public void setSchaltmonat(boolean schaltmonat) {
    this.schaltmonat = schaltmonat;
  }
}
src/de/uhilger/zeitrechnung/ereignis/ChinesischesEreignis.java
New file
@@ -0,0 +1,100 @@
/*
  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.ereignis;
import de.uhilger.zeitrechnung.ChinesischesDatum;
import de.uhilger.zeitrechnung.Datum;
import de.uhilger.zeitrechnung.Definition;
import de.uhilger.zeitrechnung.kalender.ChinesischerKalender;
import de.uhilger.zeitrechnung.kalender.ISOKalender;
import java.util.ArrayList;
import java.util.List;
/**
 * Ein Ereignis, das durch ein Datum im traditionellen
 * chinesischen Kalender definiert ist.
 *
 * <p>Soll nicht ein Ereignis bestimmt sondern in den oder vom chinesischen
 * Kalender umgerechnet werden, muss die Klasse ChinesischerKalender genutzt
 * werden.</p>
 *
 * <p>
 * Die Ereignis-Definition lautet:<br>
 * ChinesischesEreignis.getDefinition.setp1(Monat des chinesischen Kalenders);<br>
 * ChinesischesEreignis.getDefinition.setp2(Tag im Monat des chinesischen Kalenders);<br>
 * wobei Monat einer Ganzzahl aus
 * [ChinesischerKalender.noch-angeben .. ChinesischerKalender.noch-angeben]
 * entspricht.
 * </p>
 *
 * @author Ulrich Hilger
 */
public class ChinesischesEreignis extends EreignisBasis {
  @Override
  public int getTyp() {
    return EreignisBasis.TYP_CHINESISCH;
  }
  @Override
  public List<Datum> getZeitpunkte(long isoJahr) {
    Definition def = getDefinition();
    ChinesischerKalender j = new ChinesischerKalender();
    long tag = j.cDatumZuGenerisch(isoJahr, (int) def.getp1(), (int) def.getp2());
    List daten = new ArrayList();
    daten.add(new ISOKalender().vonTagen(tag));
    return daten;
  }
  public List<Datum> getZeitpunkte2(long isoJahr) {
    ISOKalender w = new ISOKalender();
    long start = w.zuTagen(isoJahr, Definition.JANUAR, 1);
    long ende = w.zuTagen(isoJahr, Definition.DEZEMBER, 31);
    ChinesischerKalender j = new ChinesischerKalender();
    Definition def = getDefinition();
    List daten = new ArrayList();
    long y = 0;
    long tag = start;
    long ersterJanuar = start;
    while(tag < ende) {
      // Neujahr vor dem 1.1. als generisches Datum
      long cNeujahr = j.neujahrAmOderVor(ersterJanuar);
      // Neujahrstag als chinesisches Datum, z.B. 7. Feb 2022 für Neujahr vor 1.1.2023
      ChinesischesDatum cdNeujahr = j.vonTagen(cNeujahr);
      // generisches Datum fuer den Tag des Ereignisses bestimmen,
      // Ereignisse fallen nie auf Schaltmonate, deshalb schaltmonat auf false
      tag = j.zuTagen(cdNeujahr.getZyklus(), (int) cdNeujahr.getJahr(), (int) def.getp1(),
              false, (int) def.getp2());
      // der Liste hinzufügen, wenn das Ereignis ins isoJahr fällt
      if (tag >= start && tag <= ende) {
        Datum d = w.vonTagen(tag);
        daten.add(d);
      }
      // für nächstes Jahr wiederholen, bis das Ereignis nicht mehr im isoJahr liegt
      ++y;
      ersterJanuar = w.zuTagen(isoJahr+y, Definition.JANUAR, 1);
    }
    return daten;
  }
}
src/de/uhilger/zeitrechnung/ereignis/EinzelEreignis.java
@@ -19,6 +19,7 @@
import de.uhilger.zeitrechnung.Datum;
import de.uhilger.zeitrechnung.Definition;
import de.uhilger.zeitrechnung.kalender.ChinesischerKalender;
import de.uhilger.zeitrechnung.kalender.HebraeischerKalender;
import de.uhilger.zeitrechnung.kalender.ISOKalender;
import java.util.ArrayList;
@@ -49,6 +50,10 @@
  
  /** Typnummer fuer Jom Ha Zikaron */
  public static final int EE_JOM_HA_ZIKARON = 4;
  public static final int EE_CHINESISCHES_NEUJAHR = 5;
  public static final int EE_QINGMING = 6;
  /**
   * Den Typ des Ereignisses ermitteln
@@ -74,26 +79,31 @@
    Definition def = getDefinition();
    int typ = (int) def.getp1();
    ISOKalender g = new ISOKalender();
    HebraeischerKalender h = new HebraeischerKalender();
    List daten = new ArrayList();
    long tage;
    switch(typ) {
      case EE_SCHAVUOT:
        tage = h.passah(isoJahr);
        tage = new HebraeischerKalender().passah(isoJahr);
        daten.add(g.vonTagen(tage + 50));
        break;
      case EE_PURIM:
        tage = h.purim(isoJahr);
        tage = new HebraeischerKalender().purim(isoJahr);
        daten.add(g.vonTagen(tage));
        break;
      case EE_TA_ANIT_ESTHER:
        tage = h.taAnitEsther(isoJahr);
        tage = new HebraeischerKalender().taAnitEsther(isoJahr);
        daten.add(g.vonTagen(tage));
        break;
      case EE_JOM_HA_ZIKARON:
        tage = h.yomHaZikkaron(isoJahr);
        tage = new HebraeischerKalender().yomHaZikkaron(isoJahr);
        daten.add(g.vonTagen(tage));
        break;
      case EE_CHINESISCHES_NEUJAHR:
        daten.add(g.vonTagen(new ChinesischerKalender().neujahr(isoJahr)));
        break;
      case EE_QINGMING:
        daten.add(g.vonTagen(new ChinesischerKalender().qingMing(isoJahr)));
        break;
    }
    return daten;
  }    
src/de/uhilger/zeitrechnung/ereignis/Ereignis.java
@@ -60,6 +60,9 @@
  /** Typnummer fuer JulianischesEreignis */
  public static final int TYP_JULIANISCH = 9;
  
  /** Typnummer fuer ChinesischesEreignis */
  public static final int TYP_CHINESISCH = 10;
  public static final int GREGORIANISCHER_KALENDER = 1;
  public static final int JULIANISCHER_KALENDER = 2;
  
src/de/uhilger/zeitrechnung/ereignis/EreignisBasis.java
@@ -32,7 +32,7 @@
    TagDatumEreignis.class.getName(), OsterEreignis.class.getName(), 
    JahreszeitEreignis.class.getName(), HebraeischesEreignis.class.getName(),
    EinzelEreignis.class.getName(), MuslimischesEreignis.class.getName(),
    JulianischesEreignis.class.getName()
    JulianischesEreignis.class.getName(), ChinesischesEreignis.class.getName()
  };
  
  /**
src/de/uhilger/zeitrechnung/ereignis/JulianischesEreignis.java
@@ -53,8 +53,7 @@
    long anzahlJahre = (jEndJahr - jStartJahr) + (long) 1;
    ArrayList list = new ArrayList();
    for (long y = 0; y < anzahlJahre; y++) {
      Datum jDatum = new Datum(isoJahr + y, (int) definition.getp1(), (int) definition.getp2());
      long tage = j.zuTagen(jDatum);
      long tage = j.zuTagen(jStartJahr + y, (int) definition.getp1(), (int) definition.getp2());
      if(tage >= start && tage <= end) {
        list.add(g.vonTagen(tage));
      }
src/de/uhilger/zeitrechnung/kalender/BasisKalender.java
@@ -22,10 +22,12 @@
import de.uhilger.zeitrechnung.Zeit;
/**
 * Abstrakte Basisklasse fuer Klassen, die ein Kalendersystem implementieren
 * Abstrakte Basisklasse fuer Klassen, die ein Kalendersystem implementieren.
 *
 * Hier sind neben allerlei relevanten Rechenmethoden die grundlegenden
 * astronomischen Algorithmen für die Zeit- und Kalenderrechnung implementiert.
 * 
 * @author Ulrich Hilger
 * @version 2, 1.10.2022
 */
public abstract class BasisKalender implements Zeitrechnung {
  
@@ -82,6 +84,12 @@
        return nterTag(-1, t, datum);
    }
  
  /* ----- */
    public double moduloAngepasst(double x, double y) {
        return y + modulo(x, -y);
    }
  /* ---- Zeit ----- */
  
    public double zuMoment(int stunde, int minute, double sekunde) {
@@ -100,7 +108,10 @@
    return z;
    }  
    
  /* ----------- Mondphase ----------- */
  /* ----------- Mond ----------- */
  /** durchschnittliche Dauer eines Mondphasenzyklus (synodischer Monat) in Tagen */
    public static final double MITTLERER_SYNODISCHER_MONAT = 29.530588853;
  
    public double mondphase(double t) {
        return modulo(mondLaenge(t) - solareLaenge(t), 360);
@@ -109,7 +120,7 @@
    public double mondHoehe(double t, Ort ort) {
        double phi = ort.getBreite();
        double psi = ort.getLaenge();
        double varepsilon = obliquity(t);
        double varepsilon = schiefstand(t);
        double lambda = mondLaenge(t);
        double beta = mondBreite(t);
        double alpha = arcTanGrad(
@@ -266,6 +277,80 @@
        private static final double[] siderealCoeff = new double[] {280.46061837, 36525 * 360.98564736629, 0.000387933, 1d/38710000};
    }
    public double neumondNach(double tee) {
        return nterNeumond(1 + Math.round(tee / MITTLERER_SYNODISCHER_MONAT - mondphase(tee) / (double) (360)));
    }
    public double neumondVor(double tee) {
        return nterNeumond(Math.round(tee / MITTLERER_SYNODISCHER_MONAT - mondphase(tee) / (double) (360)));
    }
    public double nterNeumond(long n) {
        double k = n - 24724;
        double c = k / 1236.85;
        double approx = poly(c, nm.coeffApprox);
        double capE = poly(c, nm.coeffCapE);
        double solarAnomaly = poly(c, nm.coeffSolarAnomaly);
        double lunarAnomaly = poly(c, nm.coeffLunarAnomaly);
        double moonArgument = poly(c, nm.coeffMoonArgument);
        double capOmega = poly(c, nm.coeffCapOmega);
        double correction = -0.00017 * sinGrad(capOmega);
        for(int ix = 0; ix < nm.sineCoeff.length; ++ix) {
            correction += nm.sineCoeff[ix] * Math.pow(capE, nm.EFactor[ix]) *
                sinGrad(nm.solarCoeff[ix] * solarAnomaly +
                    nm.lunarCoeff[ix] * lunarAnomaly +
                    nm.moonCoeff[ix] * moonArgument);
        }
        double additional = 0;
        for(int ix = 0; ix < nm.addConst.length; ++ix) {
            additional += nm.addFactor[ix] *
                sinGrad(nm.addConst[ix] + nm.addCoeff[ix] * k);
        }
        double extra = 0.000325 * sinGrad(poly(c, nm.extra));
        return universalVonDynamisch(approx + correction + extra + additional);
    }
    private static class nm {
        private static final double[] coeffApprox = new double[] {730125.59765, MITTLERER_SYNODISCHER_MONAT * 1236.85, 0.0001337, -0.000000150, 0.00000000073};
        private static final double[] coeffCapE = new double[] {1, -0.002516, -0.0000074};
        private static final double[] coeffSolarAnomaly = new double[] {2.5534, 29.10535669 * 1236.85, -0.0000218, -0.00000011};
        private static final double[] coeffLunarAnomaly = new double[] {201.5643, 385.81693528 * 1236.85, 0.0107438, 0.00001239, -0.000000058};
        private static final double[] coeffMoonArgument = new double[] {160.7108, 390.67050274 * 1236.85, -0.0016341, -0.00000227, 0.000000011};
        private static final double[] coeffCapOmega = new double[] {124.7746, -1.56375580 * 1236.85, 0.0020691, 0.00000215};
        private static final byte[] EFactor = new byte[] {0, 1, 0, 0, 1, 1, 2, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        private static final byte[] solarCoeff = new byte[] {0, 1, 0, 0, -1, 1, 2, 0, 0, 1, 0, 1, 1, -1, 2, 0, 3, 1, 0, 1, -1, -1, 1, 0};
        private static final byte[] lunarCoeff = new byte[] {1, 0, 2, 0, 1, 1, 0, 1, 1, 2, 3, 0, 0, 2, 1, 2, 0, 1, 2, 1, 1, 1, 3, 4};
        private static final byte[] moonCoeff = new byte[] {0, 0, 0, 2, 0, 0, 0, -2, 2, 0, 0, 2, -2, 0, 0, -2, 0, -2, 2, 2, 2, -2, 0, 0};
        private static final double[] sineCoeff = new double[] {
            -0.40720, 0.17241, 0.01608, 0.01039, 0.00739, -0.00514, 0.00208,
            -0.00111, -0.00057, 0.00056, -0.00042, 0.00042, 0.00038, -0.00024,
            -0.00007, 0.00004, 0.00004, 0.00003, 0.00003, -0.00003, 0.00003,
            -0.00002, -0.00002, 0.00002
        };
        private static final double[] addConst = new double[] {
            251.88, 251.83, 349.42, 84.66, 141.74, 207.14, 154.84, 34.52, 207.19,
            291.34, 161.72, 239.56, 331.55
        };
        private static final double[] addCoeff = new double[] {
            0.016321, 26.641886, 36.412478, 18.206239, 53.303771, 2.453732,
            7.306860, 27.261239, 0.121824, 1.844379, 24.198154, 25.513099, 3.592518
        };
        private static final double[] addFactor = new double[] {
            0.000165, 0.000164, 0.000126, 0.000110, 0.000062, 0.000060, 0.000056,
            0.000047, 0.000042, 0.000040, 0.000037, 0.000035, 0.000023
        };
        private static final double[] extra = new double[] {
            299.77, 132.8475848, -0.009173
        };
    }
    public double universalVonDynamisch(double tee) {
        return tee - ephemeridenKorrektur(tee);
    }
    public double universalVonStandard(double teeS, Ort locale) {
        return teeS - locale.getZeitzone() / 24;
    }
  /* ----------- Sonnenauf- und -untergang ----------- */
  
    public double sonnenaufgang(long date, Ort ort) throws Exception {
@@ -312,7 +397,7 @@
    public double zeitVonHorizont(double approx, Ort ort, double alpha) throws Exception {
        double phi = ort.getBreite();
        double t = universalVonLokal(approx, ort);
        double delta = arcSinGrad(sinGrad(obliquity(t)) * sinGrad(solareLaenge(t)));
        double delta = arcSinGrad(sinGrad(schiefstand(t)) * sinGrad(solareLaenge(t)));
        boolean morgen = modulo(approx, 1) < 0.5;
        double sinusAbstand = tanGrad(phi) * tanGrad(delta) +
            sinGrad(alpha) / (kosGrad(delta) * kosGrad(phi));
@@ -340,14 +425,14 @@
        double laenge = poly(c, et.koeffLaenge);
        double anomalie = poly(c, et.koeffAnomalie);
        double exzentrizitaet = poly(c, et.koeffExzentrizitaet);
        double varepsilon = obliquity(t);
        double varepsilon = schiefstand(t);
        double y = quadrat(tanGrad(varepsilon / 2));
        double equation = (1d / 2d / Math.PI) * (y * sinGrad(2 * laenge) +
        -2 * exzentrizitaet * sinGrad(anomalie) +
        4 * exzentrizitaet * y * sinGrad(anomalie) * kosGrad(2 * laenge) +
        -0.5 * y * y * sinGrad(4 * laenge) +
        -1.25 * exzentrizitaet * exzentrizitaet * sinGrad(2 * anomalie));
        return signum(equation) * Math.min(Math.abs(equation), stunde(12));
        return vorzeichen(equation) * Math.min(Math.abs(equation), stunde(12));
    }
    private static class et {
        private static final double[] koeffLaenge = new double[] {280.46645, 36000.76983, 0.0003032};
@@ -355,13 +440,13 @@
        private static final double[] koeffExzentrizitaet = new double[] {0.016708617, -0.000042037, -0.0000001236};
    }
    public double obliquity(double t) {
    public double schiefstand(double t) {
        double c = julJahrhunderte(t);
        return winkel(23, 26, 21.448) + poly(c, coeffObliquity);
    }
    private final double[] coeffObliquity = new double[] {0, winkel(0, 0, -46.8150), winkel(0, 0, -0.00059), winkel(0, 0, 0.001813)};
  
    public int signum(double x) {
    public int vorzeichen(double x) {
        if(x < 0)
            return -1;
        else if(x > 0)
@@ -400,7 +485,8 @@
  
  /* ---------------- Jahreszeiten ----- */
    public static final double TROPISCHES_JAHR = 365.242189;
  /** durchschnittliche Dauer eines Umlaufs der Erde um die Sonne in Tagen */
    public static final double MITTLERES_TROPISCHES_JAHR = 365.242189;
    
    public double standardVonUniversal(double t, Ort ort) {
        return t + ort.getZeitzone() / 24;
@@ -408,7 +494,7 @@
  
    public double solareLaengeNach(double t, double phi) {
        double varepsilon = 0.00001;
    double rate = TROPISCHES_JAHR / (double) 360;
    double rate = MITTLERES_TROPISCHES_JAHR / (double) 360;
        double tau = t + rate * modulo(phi - solareLaenge(t), 360);
        double l = Math.max(t, tau - 5);
        double u = tau + 5;
@@ -437,6 +523,13 @@
        return modulo(laenge + aberration(t) + nutation(t), 360);
    }
    public double geschaetzteSolareLaengeVor(double tee, double phi) {
        double rate = MITTLERES_TROPISCHES_JAHR / (double) (360);
        double tau = tee - rate * modulo(solareLaenge(tee) - phi, 360);
        double capDelta = modulo(solareLaenge(tau) - phi + (double) (180), 360) - (double) (180);
        return Math.min(tee, tau - rate * capDelta);
    }
    public double julJahrhunderte(double t) {
        return (dynamischVonUniversal(t) - j2000()) / 36525;
    }
src/de/uhilger/zeitrechnung/kalender/ChinesischerKalender.java
New file
@@ -0,0 +1,227 @@
/*
  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 Tagundnacht­gleichen 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 Tagundnacht­gleiche 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)));
    }
}