Bestimmung der Zeitpunkte von Ereignissen
ulrich
2023-03-23 d20d989f5495492f1258c8313db7c19b429111a3
commit | author | age
d20d98 1 /*
U 2   Zeitrechnung - a class library to determine calendar events
3   Copyright (c) 1984-2023 Ulrich Hilger, http://uhilger.de
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU Affero General Public License as published by
7   the Free Software Foundation, either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package de.uhilger.zeitrechnung.kalender;
19
20 import de.uhilger.zeitrechnung.ChinesischesDatum;
21 import de.uhilger.zeitrechnung.Datum;
22 import de.uhilger.zeitrechnung.Definition;
23 import de.uhilger.zeitrechnung.Ort;
24
25 /**
26  * Der traditionelle chinesichen Kalender ist unterteilt in Mond- und Sonnenjahr. 
27  * Das Sonnenjahr (sui) beginnt am Tag der Wintersonnenwende ist weiter unterteilt 
28  * in solare Abschnitte und noch weiter unterteilt in solare Haupt- und Unterabschnitte. 
29  * 
30  * Die Bahn, die die Sonne innerhalb eines tropischen Jahres von 365,24 Tagen scheinbar 
31  * auf der Ekliptik durchläuft, wird in 24 Teile von je 15 Grad unterteilt. Dies sind die 24 
32  * Stationen oder Jahreseinteilungen (節氣 / 节气, jiéqì). Jede zweite Station ist ein 
33  * Zhongqi (中氣 / 中气, zhōngqì – „zentrale/Haupt-Jahreseinteilung“), wobei die Sonnenwenden 
34  * und Tagundnacht­gleichen vier der zwölf Zhongqi sind. Der zeitliche Abstand von einem 
35  * Zhongqi zum nächsten beträgt im Mittel ein Zwölftel eines tropischen Jahres oder 30,44 Tage. 
36  * Er variiert leicht aufgrund der elliptischen Umlaufbahn der Erde um die Sonne. 
37  * 
38  * Jeweils sechs Jahreseinteilungen gehören zu einer Jahreszeit. Während aber im westlichen 
39  * Kalender die Jahreszeiten mit dem Tag der Sonnenwende bzw. der Tagundnacht­gleiche beginnen, 
40  * liegen im chinesischen Kalender diese Tage in der Mitte der jeweiligen Jahreszeit. Man zählt 
41  * die Stationen beginnend mit dem Frühlingsanfang – lichun, und mancherorts gilt der 
42  * Frühlingsanfang (und nicht die Wintersonnenwende) als Beginn des sui-Jahres.
43  * 
44  * Eine Besonderheit des Chinesischen Kalenders ist, dass ein Chinesisches Datum neben 
45  * Tag, Monat und Jahr auch den Zyklus und die Angabe zum Schaltmonat benoetigt. Um 
46  * die Schnittstelle Wandler vollstaendig implementieren zu koennen, werden 
47  * die Methoden setZyklus und setSchaltmonat zusaetzlich eingebaut.
48  * 
49  * Bei Nutzung der Wandler-Schnittstelle sollte im Falle des Chinesischen Kalenders 
50  * darauf geachtet werden, dass der Methode zuTagen() entweder ein Datum der 
51  * Klasse ChinesischesDatum uebergeben wird oder zuvor setZyklus und setSchaltmonat genutzt wird.
52  * Auch vor Nutzung der Methode zuTagen(jahr, monat, tag) muss zuvor setZyklus und 
53  * setSchaltmonat genutzt oder stattdessen die Methode 
54  * zuTagen(zyklus, jahr, monat, schaltmonat, tag) verwendet werden.
55  * 
56  * @author Ulrich Hilger
57  */
58 public class ChinesischerKalender extends BasisKalender implements Wandler {
59
60     public static final long STARTTAG = new ISOKalender().zuTagen(-2636, Definition.FEBRUAR, 15);
61   
62   private long zyklus;
63   private boolean schaltmonat;
64
65   public void setZyklus(long zyklus) {
66     this.zyklus = zyklus;
67   }
68
69   public void setSchaltmonat(boolean schaltmonat) {
70     this.schaltmonat = schaltmonat;
71   }
72   
73     public long zuTagen(long zyklus, int jahr, int monat, boolean schaltmonat, int tag) {
74     this.zyklus = zyklus;
75     this.schaltmonat = schaltmonat;
76     return zuTagen(jahr, monat, tag);
77     }
78
79   @Override
80   public long zuTagen(long jahr, int monat, int tag) {    
81         long midYear = (long)Math.floor(STARTTAG + ((zyklus - 1) * 60 + (jahr - 1) + .5) * MITTLERES_TROPISCHES_JAHR);
82         long theNewYear = neujahrAmOderVor(midYear);
83         long p = chinesischerNeumondAmOderNach(theNewYear + 29 * (monat - 1));    
84         ChinesischesDatum d = vonTagen(p);
85         long priorNewMoon = monat == d.getMonat() && schaltmonat == d.isSchaltmonat() ?
86             p :
87             chinesischerNeumondAmOderNach(p + 1);
88         return priorNewMoon + tag - 1;
89   }
90
91   @Override
92   public long zuTagen(Datum d) {
93     if(d instanceof ChinesischesDatum) {
94       ChinesischesDatum cd = (ChinesischesDatum) d;
95       return zuTagen(cd.getZyklus(), (int) cd.getJahr(), cd.getMonat(), cd.isSchaltmonat(), cd.getTag());
96     } else {
97       return zuTagen(zyklus, (int) d.getJahr(), d.getMonat(), schaltmonat, d.getTag());
98     }
99   }
100
101   @Override
102   public ChinesischesDatum vonTagen(long tage) {
103         long s1 = winterSonnenwendeAmOderVor(tage);
104         long s2 = winterSonnenwendeAmOderVor(s1 + 370);
105         long m12 = chinesischerNeumondAmOderNach(s1 + 1);
106         long nextM11 = chinesischerNeumondVor(s2 + 1);
107         long m = chinesischerNeumondVor(tage + 1);
108         boolean leapYear = Math.round((nextM11 - m12) / MITTLERER_SYNODISCHER_MONAT) == 12;
109         int month = (int)moduloAngepasst(
110             (long)Math.round((m - m12) / MITTLERER_SYNODISCHER_MONAT) -
111             (leapYear && hatSchaltmonatVorher(m12, m) ? 1 : 0),
112             12);
113         boolean leapMonth = leapYear &&
114             keinSolarerHauptabschnitt(m) &&
115             !hatSchaltmonatVorher(m12, chinesischerNeumondVor(m));
116         long elapsedYears = (long)Math.floor(1.5 - (month / 12d) + (tage - STARTTAG) / MITTLERES_TROPISCHES_JAHR);
117         long cycle = ganzzahlQuotient(elapsedYears - 1, 60) + 1;
118         int year = (int)moduloAngepasst(elapsedYears, 60);
119         int day = (int)(tage - m + 1);    
120     return new ChinesischesDatum(cycle, year, month, leapMonth, day);
121   }
122   
123   /* ---- */
124   
125     public boolean hatSchaltmonatVorher(long mPrime, long m) {
126         return m >= mPrime && (keinSolarerHauptabschnitt(m) || hatSchaltmonatVorher(mPrime, chinesischerNeumondVor(m)));
127     }
128     
129     public double mitternachtInChina(long date) {
130         return universalVonStandard(date, chinesischerOrt(date));
131     }
132     
133     public final Ort peking(double tee) {
134     ISOKalender g = new ISOKalender();
135         long year = g.jahrVonTagen((long)Math.floor(tee));
136         return new Ort("Peking", (double) (39.55), winkel(116,25,0), 
137             (double) (43.5), year < 1929 ? 1397d/180 : 8);
138     }
139     
140     public final Ort chinesischerOrt(double tee) {
141         return peking(tee);
142     }
143     
144     public long neujahrAmOderVor(long tage) {
145         long neujahr = neujahrInSui(tage);
146         return tage >= neujahr ? neujahr : neujahrInSui(tage - 180);
147     }
148     
149     public long neujahrInSui(long tage) {
150         long s1 = winterSonnenwendeAmOderVor(tage);
151         long s2 = winterSonnenwendeAmOderVor(s1 + 370);
152         long m12 = chinesischerNeumondAmOderNach(s1 + 1);
153         long m13 = chinesischerNeumondAmOderNach(m12 + 1);
154         long nextM11 = chinesischerNeumondVor(s2 + 1);
155         if((Math.round((nextM11 - m12) / MITTLERER_SYNODISCHER_MONAT) == 12) && (keinSolarerHauptabschnitt(m12) || keinSolarerHauptabschnitt(m13))) {
156             return chinesischerNeumondAmOderNach(m13 + 1);
157         } else {
158             return m13;
159         }
160     }
161
162     public long neujahr(long gYear) {
163     ISOKalender g = new ISOKalender();
164         return neujahrAmOderVor(g.zuTagen(gYear, Definition.JULI, 1));
165     }
166
167     public long qingMing(long gYear) {
168     ISOKalender g = new ISOKalender();
169         return (long)Math.floor(solarerNebenabschnittAmOderNach(g.zuTagen(gYear, Definition.MAERZ, 30)));
170     }
171   
172     public long cDatumZuGenerisch(long isoJahr, int cMonat, int cTag) {
173     ISOKalender g = new ISOKalender();
174         long elapsedYears = isoJahr - g.jahrVonTagen(STARTTAG) + 1;
175         long cycle = ganzzahlQuotient(elapsedYears - 1, 60) + 1;
176         int year = (int)moduloAngepasst(elapsedYears, 60);
177         return zuTagen(cycle, year, cMonat, false, cTag);
178     }
179     
180     public long winterSonnenwendeAmOderVor(long date) {
181         double approx = geschaetzteSolareLaengeVor(mitternachtInChina(date + 1), Definition.WINTER);
182         long i;
183         for(i = (long)(Math.floor(approx) - 1); !(Definition.WINTER <= solareLaenge(mitternachtInChina(i + 1))); ++i);
184         return i;
185     }
186       
187     public boolean keinSolarerHauptabschnitt(long date) {
188         return aktuellerSolarerHauptabschnitt(date) ==
189             aktuellerSolarerHauptabschnitt(chinesischerNeumondAmOderNach(date + 1));
190     }
191
192     public int aktuellerSolarerHauptabschnitt(long date) {
193         double s = solareLaenge(standardVonUniversal(date, chinesischerOrt(date)));
194         return (int)moduloAngepasst(2 + ganzzahlQuotient(s, (double) (30)), 12);
195     }
196
197     public double solarerHauptabschnittAmOderNach(long date) {
198         double l = modulo(30 * Math.ceil(solareLaenge(mitternachtInChina(date)) / 30), 360);
199         return chinesischeSolareLaengeAmOderNach(date, l);
200     }
201   
202     public double solarerNebenabschnittAmOderNach(long date) {
203         double l = modulo(30 * Math.ceil((solareLaenge(mitternachtInChina(date)) - (double) (15)) / 30) + (double) (15), 360);
204         return chinesischeSolareLaengeAmOderNach(date, l);
205     }
206     
207     public double chinesischeSolareLaengeAmOderNach(long date, double theta) {
208         Ort beijing = chinesischerOrt(date);
209         double tee = solareLaengeNach(standardVonUniversal(date, beijing), theta);
210         return standardVonUniversal(tee, beijing);
211     }
212     
213     public int aktuellerSolarerUnterabschnitt(long date) {
214         double s = solareLaenge(mitternachtInChina(date));
215         return (int)moduloAngepasst(3 + ganzzahlQuotient(s - (double) (15), (double) (30)), 12);
216     }
217
218     public long chinesischerNeumondAmOderNach(long date) {
219         double t = neumondNach(mitternachtInChina(date));
220         return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
221     }
222
223     public long chinesischerNeumondVor(long date) {
224     double t = neumondVor((long) mitternachtInChina(date));
225         return (long)Math.floor(standardVonUniversal(t, chinesischerOrt(t)));
226     }
227 }