1#![forbid(unsafe_code)]
13
14mod delta_t;
15mod elpmpp02_data;
16mod julian;
17mod lunisolar;
18mod moon;
19mod names;
20mod new_moon;
21mod nutation_data;
22mod solar_terms;
23mod vsop87d_earth;
24
25pub use delta_t::delta_t_for_year;
26pub use julian::{jd_from_ymd, ymd_from_jd};
27pub use lunisolar::{
28 gregorian_to_lunisolar, gregorian_to_lunisolar_with, lunar_months_for_year, lunar_new_year,
29 CivilDate, LunarMonth, LunisolarDate,
30};
31pub use moon::{moon_position, MoonState};
32pub use names::{EARTHLY_BRANCHES, HEAVENLY_STEMS};
33pub use new_moon::{find_new_moons_in_range, new_moon_jde};
34pub use solar_terms::{
35 find_solar_term_moment, solar_term_for_longitude, SOLAR_TERM_LONGITUDES, SOLAR_TERM_NAMES,
36};
37
38use core::f64::consts::{PI, TAU};
39use nutation_data::{NUT_COEFFS, NUT_OBLIQ};
40use vsop87d_earth::{EARTH_L, EARTH_R};
41
42pub(crate) const ARCSEC_TO_RAD: f64 = PI / 180.0 / 3600.0;
44pub(crate) const RAD_TO_DEG: f64 = 180.0 / PI;
46
47#[derive(Debug, Clone, Copy, PartialEq)]
49pub struct SolarState {
50 pub true_longitude_degrees: f64,
53 pub apparent_longitude_degrees: f64,
56 pub radius_au: f64,
58}
59
60pub fn solar_ecliptic_state(jde_tt: f64) -> SolarState {
63 let tau = (jde_tt - 2451545.0) / 365250.0; let t = (jde_tt - 2451545.0) / 36525.0; let mut lon = eval_vsop_series(EARTH_L, tau);
68 let r = eval_vsop_series(EARTH_R, tau);
69
70 let tau2 = tau * tau;
73 lon += (-0.106674 - 0.616597 * tau2 + 0.315446 * tau2 * tau2 - 0.050315 * tau2 * tau2 * tau2)
74 / 206264.806;
75
76 let geo_true = lon + PI;
79
80 let (dl, dlp, df, dd, dom) = delaunay_args(t);
82 let dpsi = nutation_dpsi(dl, dlp, df, dd, dom, t);
83 let apparent = geo_true + dpsi * ARCSEC_TO_RAD + (-20.4898 / r) * ARCSEC_TO_RAD;
84
85 SolarState {
86 true_longitude_degrees: normalize_radians(geo_true) * RAD_TO_DEG,
87 apparent_longitude_degrees: normalize_radians(apparent) * RAD_TO_DEG,
88 radius_au: r,
89 }
90}
91
92pub(crate) const AU_KM: f64 = 149_597_870.7;
94
95#[derive(Debug, Clone, Copy, PartialEq)]
101pub struct MoonPhase {
102 pub elongation_deg: f64,
106 pub phase_angle_deg: f64,
109 pub illuminated_fraction: f64,
112 pub waxing: bool,
115}
116
117#[must_use]
126pub fn moon_phase(jde_tt: f64) -> MoonPhase {
127 let moon = moon_position(jde_tt);
128 let sun = solar_ecliptic_state(jde_tt);
129
130 let lam_m = moon.longitude_degrees.to_radians();
131 let lam_s = sun.apparent_longitude_degrees.to_radians();
132 let beta = moon.latitude_degrees.to_radians();
133
134 let cos_psi = beta.cos() * (lam_m - lam_s).cos();
136 let psi = cos_psi.clamp(-1.0, 1.0).acos(); let r_km = sun.radius_au * AU_KM;
140 let delta_km = moon.distance_km;
141 let i = (r_km * psi.sin()).atan2(delta_km - r_km * cos_psi); let illuminated_fraction = (1.0 + i.cos()) / 2.0;
143
144 let elongation_deg =
146 (moon.longitude_degrees - sun.apparent_longitude_degrees).rem_euclid(360.0);
147
148 MoonPhase {
149 elongation_deg,
150 phase_angle_deg: i.to_degrees(),
151 illuminated_fraction,
152 waxing: elongation_deg < 180.0,
153 }
154}
155
156fn eval_vsop_series(series: &[&[[f64; 3]]], tau: f64) -> f64 {
159 let mut result = 0.0;
160 let mut tau_pow = 1.0;
161 for terms in series {
162 let mut sum = 0.0;
163 for term in *terms {
164 sum += term[0] * (term[1] + term[2] * tau).cos();
165 }
166 result += sum * tau_pow;
167 tau_pow *= tau;
168 }
169 result
170}
171
172pub(crate) fn delaunay_args(t: f64) -> (f64, f64, f64, f64, f64) {
175 let t2 = t * t;
176 let t3 = t2 * t;
177 let t4 = t3 * t;
178 let l = ((485868.249036 + 1717915923.2178 * t + 31.8792 * t2 + 0.051635 * t3
181 - 0.00024470 * t4)
182 % 1296000.0)
183 * ARCSEC_TO_RAD;
184 let lp = ((1287104.79305 + 129596581.0481 * t - 0.5532 * t2 + 0.000136 * t3 - 0.00001149 * t4)
185 % 1296000.0)
186 * ARCSEC_TO_RAD;
187 let f = ((335779.526232 + 1739527262.8478 * t - 12.7512 * t2 - 0.001037 * t3
188 + 0.00000417 * t4)
189 % 1296000.0)
190 * ARCSEC_TO_RAD;
191 let d = ((1072260.70369 + 1602961601.2090 * t - 6.3706 * t2 + 0.006593 * t3 - 0.00003169 * t4)
192 % 1296000.0)
193 * ARCSEC_TO_RAD;
194 let om = ((450160.398036 - 6962890.5431 * t + 7.4722 * t2 + 0.007702 * t3 - 0.00005939 * t4)
195 % 1296000.0)
196 * ARCSEC_TO_RAD;
197 (l, lp, f, d, om)
198}
199
200pub(crate) fn nutation_dpsi(l: f64, lp: f64, f: f64, d: f64, om: f64, t: f64) -> f64 {
202 let mut dpsi = 0.0;
203 for row in NUT_COEFFS {
204 let arg = row[0] * l + row[1] * lp + row[2] * f + row[3] * d + row[4] * om;
205 dpsi += (row[5] + row[6] * t) * arg.sin();
206 }
207 dpsi / 1e7
209}
210
211pub(crate) fn nutation_deps(l: f64, lp: f64, f: f64, d: f64, om: f64, t: f64) -> f64 {
213 let mut deps = 0.0;
214 for (row, obliq) in NUT_COEFFS.iter().zip(NUT_OBLIQ) {
215 let arg = row[0] * l + row[1] * lp + row[2] * f + row[3] * d + row[4] * om;
216 deps += (obliq[0] + obliq[1] * t) * arg.cos();
217 }
218 deps / 1e7
220}
221
222pub(crate) fn normalize_radians(rad: f64) -> f64 {
224 ((rad % TAU) + TAU) % TAU
225}