// ################################################################
// Hilfs-Funktionen
// Faktor für Grad => Radiant
const DEGS = Math.PI/180;
// Ganzzahl Anteil und Komma-Anteil einer Zahl (truncation)
const ipart = n => n >= 0 ? Math.floor(n) : Math.ceil(n);
const fpart = n => n >= 0 ? n - ipart(n) : n + ipart(-n);
// Große Winkelwerte in das Intervall [0°-360°] bringen
const range = deg => (deg % 360 + 360) % 360;
// Winkelfuntionen für Argumente in Dezimalgrad
const sin = x => Math.sin(x*DEGS);
const cos = x => Math.cos(x*DEGS);
// ---------------------------
// Schaltjahr (ja/nein == 1/0)
// ---------------------------
function isLeap(y) {
if (y > 1582) {
// Gregorianischer Kalender
return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) ? 1 : 0;
}
else {
// Julianischer Kalender
return (y % 4 == 0) ? 1 : 0;
}
}
// -----------------------------------------------------------------------
// Rückrechnung der Julianischen Tagzahl in ein bürgerliches Kalenderdatum
// Ergebnis in Weltzeit UT
// -----------------------------------------------------------------------
function JDtoCal(jd) {
let Z, F, A, alpha, B, C, D, E;
let yrs, mth, dys, dysHMS;
jd += 0.5;
Z = ipart(jd);
F = fpart(jd);
if (Z < 2299161) {
A = Z;
} else {
alpha = ipart((Z - 1867216.25) / 36524.25);
A = Z + 1 + alpha - ipart(alpha / 4);
}
//
B = A + 1524;
C = ipart((B - 122.1) / 365.25);
D = ipart(365.25*C);
E = ipart((B - D) / 30.6001);
//
dys = B - D - ipart(30.6001*E) + F;
E < 14 ? mth = E - 1 : mth = E - 13;
mth > 2 ? yrs = C - 4716 : yrs = C - 4715;
// Dezimaltag in h/m/s umrechnen
dysHMS = toHMS(dys);
return timeCorr(yrs, mth, ipart(dys), dysHMS[0], dysHMS[1], parseFloat(dysHMS[2].toFixed(1)));
}
// -----------------------------------------------------------------
// Funktion zur Korrektur von Überläufen bei der Umwandlung
// von JD zurück in ein Kalenderdatum. Durch Rundung könnten Werte
// wie z.B. '60 Sekunden' auftreten.
// -----------------------------------------------------------------
function timeCorr(yrs, mth, dys, hrs, mins, secs) {
const md = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// Februar Tage
md[1] = isLeap(yrs) ? 29 : 28;
if (secs >= 60) {
secs -= 60;
mins += 1;
}
if (mins >= 60) {
mins -= 60;
hrs += 1;
}
if (hrs >= 24) {
hrs -= 24;
dys += 1;
}
// Monate-Tage-Problem lösen
if (dys > md[mth - 1]) {
dys -= md[mth - 1];
mth += 1;
}
if (mth > 12) {
mth -= 12;
yrs += 1;
}
return [yrs, mth, dys, hrs, mins, secs];
}
// Dezimaltag in Stunden/Minuten/Sekunden umrechnen
function toHMS(dd, dp = 1) {
let hrs, hrsip, mins, minsip, secs, corr;
hrs = fpart(dd)*24;
hrsip = ipart(hrs);
mins = fpart(hrs)*60;
minsip = ipart(mins);
secs = parseFloat((fpart(mins)*60).toFixed(dp));
return hmsCorr(hrsip, minsip, secs);
}
// Korrektur für Rundungsfehler bei der
// Umrechnung von Dezimaltag in h,m,s
function hmsCorr(h, m, s) {
if (s >= 60) {
s -= 60;
m += 1;
}
if (m >= 60) {
m -= 60;
h += 1;
}
if (h >= 24) {
h -= 24;
}
return [h, m, s];
}
// ################################################################
// ################################################################
// Hauptscript Mondphase ermitteln
// ################################################################
/*
getCycleEstimate(year, month) Gibt eine Schätzung des Mondphasen-Zyklus zurück, die an getPhaseDate() übergeben werden soll. Da es sich hierbei nur um eine Schätzung handelt, sollte der Benutzer auch die Phasendaten für Zyklen vor und nach der von dieser Funktion zurückgegebenen Zyklusnummer berechnen.
Zyklus 1 => Neumond 2000-01-06, 18:13:31 UT
year - Jahreszahl, 4-stellig, Integer
month - Monatszahl, Integer, Januar = 0, Februar = 1, ... , Dezember = 11
*/
function getCycleEstimate(year, month) {
const yearfrac = (month*30 + 15) / 365; // Schätzen für den Jahres-Bruchteil
let k = 12.3685*((year + yearfrac) - 2000);
return Math.floor(k);
}
/*
Gibt die angegebene Phase innerhalb einer bestimmten Zyklusnummer basierend auf dem Jahr 2000 zurück.
Die Zyklusschätzung wird mit der oben genannten Funktion 'getCycleEstimate()' berechnet.
Zyklus – Integer aus 'getCycleEstimate()'
Phase:
0 = Neumond,
0.25 = 1. Viertel,
0.5 = Vollmond,
0.75 = letztes Viertel;
Alle anderen Werte sind ungültig!
Gibt die Julianische Tagzahl JDE der angegebenen Phase in der TD-Zeitskala zurück
*/
// J. Meeus, Astronomical Algorithms, Kap. 49
function getPhaseDate(cycle, phase) {
let correction;
const k = cycle + phase;
const T = k / 1236.85;
let JDE = 2451550.09766 + 29.530588861*k + 0.00015437*T*T - 0.000000150*T*T*T + 0.00000000073*T*T*T*T;
const E = 1 - 0.002516*T - 0.0000074*T*T;
const M = range(2.5534 + 29.10535670*k - 0.0000014*T*T - 0.00000011*T*T*T);
const Mp = range(201.5643 + 385.81693528*k + 0.0107582*T*T + 0.00001238*T*T*T - 0.000000058*T*T*T*T);
const F = range(160.7108 + 390.67050284*k - 0.0016118*T*T - 0.00000227*T*T*T + 0.000000011*T*T*T*T);
const Om = range(124.7746 - 1.56375588*k + 0.0020672*T*T + 0.00000215*T*T*T);
// Planetenterme
const A1 = range(299.77 + 0.107408*k - 0.009173*T*T);
const A2 = range(251.88 + 0.016321*k);
const A3 = range(251.83 + 26.651886*k);
const A4 = range(349.42 + 36.412478*k);
const A5 = range(84.66 + 18.206239*k);
const A6 = range(141.74 + 53.303771*k);
const A7 = range(207.14 + 2.453732*k);
const A8 = range(154.84 + 7.306860*k);
const A9 = range(34.52 + 27.261239*k);
const A10 = range(207.19 + 0.121824*k);
const A11 = range(291.34 + 1.844379*k);
const A12 = range(161.72 + 24.198154*k);
const A13 = range(239.56 + 25.513099*k);
const A14 = range(331.55 + 3.592518*k);
// Phasenkorrekturen
if (phase == 0) {
correction = +0.00002*sin(4*Mp)
-0.00002*sin(3*Mp + M)
-0.00002*sin(Mp - M - 2*F)
+0.00003*sin(Mp - M + 2*F)
-0.00003*sin(Mp + M + 2*F)
+0.00003*sin(2*Mp + 2*F)
+0.00003*sin(Mp + M - 2*F)
+0.00004*sin(3*M)
+0.00004*sin(2*Mp - 2*F)
-0.00007*sin(Mp + 2*M)
-0.00017*sin(Om)
-0.00024*E*sin(2*Mp - M)
+0.00038*E*sin(M - 2*F)
+0.00042*E*sin(M + 2*F)
-0.00042*sin(3*Mp)
+0.00056*E*sin(2*Mp + M)
-0.00057*sin(Mp + 2*F)
-0.00111*sin(Mp - 2*F)
+0.00208*E*E*sin(2*M)
-0.00514*E*sin(Mp + M)
+0.00739*E*sin(Mp - M)
+0.01039*sin(2*F)
+0.01608*sin(2*Mp)
+0.17241*E*sin(M)
-0.40720*sin(Mp);
} else if ((phase == 0.25) || (phase == 0.75)) {
correction = -0.00002*sin(3*Mp + M)
+0.00002*sin(Mp - M + 2*F)
+0.00002*sin(2*Mp - 2*F)
+0.00003*sin(3*M)
+0.00003*sin(Mp + M - 2*F)
+0.00004*sin(Mp - 2*M)
-0.00004*sin(Mp + M + 2*F)
+0.00004*sin(2*Mp + 2*F)
-0.00005*sin(Mp - M - 2*F)
-0.00017*sin(Om)
+0.00027*E*sin(2*Mp + M)
-0.00028*E*E*sin(Mp + 2*M)
+0.00032*E*sin(M - 2*F)
+0.00032*E*sin(M + 2*F)
-0.00034*E*sin(2*Mp - M)
-0.00040*sin(3*Mp)
-0.00070*sin(Mp + 2*F)
-0.00180*sin(Mp - 2*F)
+0.00204*E*E*sin(2*M)
+0.00454*E*sin(Mp - M)
+0.00804*sin(2*F)
+0.00862*sin(2*Mp)
-0.01183*E*sin(Mp + M)
+0.17172*E*sin(M)
-0.62801*sin(Mp);
const W = +0.00306 - 0.00038*E*cos(M)
+0.00026*cos(Mp)
-0.00002*cos(Mp - M)
+0.00002*cos(Mp + M)
+0.00002*cos(2*F);
if (phase == 0.25) {
correction += W;
} else {
correction -= W;
}
} else if (phase == 0.5) {
correction = +0.00002*sin(4*Mp)
-0.00002*sin(3*Mp + M)
-0.00002*sin(Mp - M - 2*F)
+0.00003*sin(Mp - M + 2*F)
-0.00003*sin(Mp + M + 2*F)
+0.00003*sin(2*Mp + 2*F)
+0.00003*sin(Mp + M - 2*F)
+0.00004*sin(3*M)
+0.00004*sin(2*Mp- 2*F)
-0.00007*sin(Mp + 2*M)
-0.00017*sin(Om)
-0.00024*E*sin(2*Mp - M)
+0.00038*E*sin(M - 2*F)
+0.00042*E*sin(M + 2*F)
-0.00042*sin(3*Mp)
+0.00056*E*sin(2*Mp + M)
-0.00057*sin(Mp + 2*F)
-0.00111*sin(Mp - 2*F)
+0.00209*E*E*sin(2*M)
-0.00514*E*sin(Mp + M)
+0.00734*E*sin(Mp - M)
+0.01043*sin(2*F)
+0.01614*sin(2*Mp)
+0.17302*E*sin(M)
-0.40614*sin(Mp);
}
JDE += correction;
// Zusatzkorrekturen
correction = +0.000325*sin(A1)
+0.000165*sin(A2)
+0.000164*sin(A3)
+0.000126*sin(A4)
+0.000110*sin(A5)
+0.000062*sin(A6)
+0.000060*sin(A7)
+0.000056*sin(A8)
+0.000047*sin(A9)
+0.000042*sin(A10)
+0.000040*sin(A11)
+0.000037*sin(A12)
+0.000035*sin(A13)
+0.000023*sin(A14);
JDE += correction;
return JDE;
}
// =========================================================
// Test für 2026
// =========================================================
const year = 2026;
const deltaT = 71; // ≈ momentaner Wert von deltaT (2026)
const cycle = getCycleEstimate(year, 1) - 2;
console.log('Jahr=', year);
console.log('cycle=', cycle);
// Alle Neumonde
for (j = 0; j < 13; j++) {
let moonUT = getPhaseDate(cycle + j, 0) - deltaT / 86400;
console.log(`Neu JD = ${moonUT}\t UT = ${JDtoCal(moonUT)}`);
}
console.log('-----------------------------------------------------------');
// Alle 1. Viertel
for (j = 0; j < 13; j++) {
let moonUT = getPhaseDate(cycle + j, 0.25) - deltaT / 86400;
console.log(`1.V. JD = ${moonUT}\t UT = ${JDtoCal(moonUT)}`);
}
console.log('-----------------------------------------------------------');
// Alle Vollmonde
for (j = 0; j < 13; j++) {
let moonUT = getPhaseDate(cycle + j, 0.5) - deltaT / 86400;
console.log(`Voll JD = ${moonUT}\t UT = ${JDtoCal(moonUT)}`);
}
console.log('-----------------------------------------------------------');
// Alle letzen Viertel
for (j = 0; j < 13; j++) {
let moonUT = getPhaseDate(cycle + j, 0.75) - deltaT / 86400;
console.log(`L.V. JD = ${moonUT}\t UT = ${JDtoCal(moonUT)}`);
}