Bestimmung der Zeitpunkte von Ereignissen
ulrich
2023-03-23 d20d989f5495492f1258c8313db7c19b429111a3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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)));
    }
}