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 Tagundnachtgleichen 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 Tagundnachtgleiche 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 |
} |