1use super::instant::Time;
54use super::scales::UT;
55use super::JulianDate;
56use qtty::{Days, Seconds, Simplify};
57
58const TERMS: usize = 187;
60
61#[rustfmt::skip]
63const DELTA_T: [Seconds; TERMS] = qtty::qtty_vec!(
64 Seconds;
65 124.0,115.0,106.0, 98.0, 91.0, 85.0, 79.0, 74.0, 70.0, 65.0,
66 62.0, 58.0, 55.0, 53.0, 50.0, 48.0, 46.0, 44.0, 42.0, 40.0,
67 37.0, 35.0, 33.0, 31.0, 28.0, 26.0, 24.0, 22.0, 20.0, 18.0,
68 16.0, 14.0, 13.0, 12.0, 11.0, 10.0, 9.0, 9.0, 9.0, 9.0,
69 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 10.0, 10.0, 10.0, 11.0,
70 11.0, 11.0, 11.0, 11.0, 11.0, 11.0, 12.0, 12.0, 12.0, 12.0,
71 12.0, 12.0, 13.0, 13.0, 13.0, 13.0, 14.0, 14.0, 14.0, 15.0,
72 15.0, 15.0, 15.0, 16.0, 16.0, 16.0, 16.0, 16.0, 17.0, 17.0,
73 17.0, 17.0, 17.0, 17.0, 17.0, 17.0, 16.0, 16.0, 15.0, 14.0,
74 13.7, 13.1, 12.7, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.3,
75 12.0, 11.4, 10.6, 9.6, 8.6, 7.5, 6.6, 6.0, 5.7, 5.6,
76 5.7, 5.9, 6.2, 6.5, 6.8, 7.1, 7.3, 7.5, 7.7, 7.8,
77 7.9, 7.5, 6.4, 5.4, 2.9, 1.6, -1.0, -2.7, -3.6, -4.7,
78 -5.4, -5.2, -5.5, -5.6, -5.8, -5.9, -6.2, -6.4, -6.1, -4.7,
79 -2.7, 0.0, 2.6, 5.4, 7.7, 10.5, 13.4, 16.0, 18.2, 20.2,
80 21.2, 22.4, 23.5, 23.9, 24.3, 24.0, 23.9, 23.9, 23.7, 24.0,
81 24.3, 25.3, 26.2, 27.3, 28.2, 29.1, 30.0, 30.7, 31.4, 32.2,
82 33.1, 34.0, 35.0, 36.5, 38.3, 40.2, 42.2, 44.5, 46.5, 48.5,
83 50.5, 52.2, 53.8, 54.9, 55.8, 56.9, 58.3,
84);
85
86const OBSERVED_TERMS: usize = 34;
94const OBSERVED_START_YEAR: f64 = 1992.0;
95
96#[rustfmt::skip]
97const OBSERVED_DT: [Seconds; OBSERVED_TERMS] = qtty::qtty_vec!(
98 Seconds;
99 58.31, 59.12, 59.98, 60.78, 61.63, 62.30, 62.97, 63.47,
101 63.83, 64.09, 64.30, 64.47, 64.57, 64.69, 64.85, 65.15,
103 65.46, 65.78, 66.07, 66.32, 66.60, 66.91, 67.28, 67.64,
105 68.10, 68.59, 68.97, 69.22, 69.36, 69.36, 69.29, 69.18,
107 69.09, 69.36,
109);
110
111const OBSERVED_END_YEAR: f64 = OBSERVED_START_YEAR + OBSERVED_TERMS as f64;
113
114const EXTRAPOLATION_RATE: f64 = 0.02;
117
118#[inline]
125fn delta_t_ancient(jd: JulianDate) -> Seconds {
126 const DT_A0_S: Seconds = Seconds::new(1_830.0);
127 const DT_A1_S: Seconds = Seconds::new(-405.0);
128 const DT_A2_S: Seconds = Seconds::new(46.5);
129 const JD_EPOCH_948_UT: JulianDate = JulianDate::new(2_067_314.5);
130 let c = days_ratio(jd - JD_EPOCH_948_UT, JulianDate::JULIAN_CENTURY);
131 DT_A0_S + DT_A1_S * c + DT_A2_S * c * c
132}
133
134#[inline]
137fn delta_t_medieval(jd: JulianDate) -> Seconds {
138 const JD_EPOCH_1850_UT: JulianDate = JulianDate::new(2_396_758.5);
139 const DT_A2_S: Seconds = Seconds::new(22.5);
140
141 let c = days_ratio(jd - JD_EPOCH_1850_UT, JulianDate::JULIAN_CENTURY);
142 DT_A2_S * c * c
143}
144
145#[inline]
148fn delta_t_table(jd: JulianDate) -> Seconds {
149 const JD_TABLE_START_1620: JulianDate = JulianDate::new(2_312_752.5);
150 const BIENNIAL_STEP_D: Days = Days::new(730.5);
151
152 let mut i = days_ratio(jd - JD_TABLE_START_1620, BIENNIAL_STEP_D) as usize;
153 if i > TERMS - 3 {
154 i = TERMS - 3;
155 }
156 let a: Seconds = DELTA_T[i + 1] - DELTA_T[i];
157 let b: Seconds = DELTA_T[i + 2] - DELTA_T[i + 1];
158 let c: Seconds = a - b;
159 let n = days_ratio(
160 jd - (JD_TABLE_START_1620 + BIENNIAL_STEP_D * i as f64),
161 BIENNIAL_STEP_D,
162 );
163 DELTA_T[i + 1] + n / 2.0 * (a + b + n * c)
164}
165
166#[inline]
169fn delta_t_observed(jd: JulianDate) -> Seconds {
170 let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
172 let idx_f = year - OBSERVED_START_YEAR;
173 let idx = idx_f as usize;
174
175 if idx + 1 >= OBSERVED_TERMS {
176 return OBSERVED_DT[OBSERVED_TERMS - 1];
178 }
179
180 let frac = idx_f - idx as f64;
182 OBSERVED_DT[idx] + frac * (OBSERVED_DT[idx + 1] - OBSERVED_DT[idx])
183}
184
185#[inline]
192fn delta_t_extrapolated(jd: JulianDate) -> Seconds {
193 let year = 2000.0 + (jd - JulianDate::J2000).value() / 365.25;
194 let dt_last = OBSERVED_DT[OBSERVED_TERMS - 1];
195 let years_past = year - OBSERVED_END_YEAR;
196 dt_last + Seconds::new(EXTRAPOLATION_RATE * years_past)
197}
198
199#[inline]
200fn days_ratio(num: Days, den: Days) -> f64 {
201 (num / den).simplify().value()
202}
203
204const JD_1992: JulianDate = JulianDate::new(2_448_622.5);
206
207const JD_2026: JulianDate = JulianDate::new(2_461_041.5);
209
210#[inline]
212pub(crate) fn delta_t_seconds_from_ut(jd_ut: JulianDate) -> Seconds {
213 match jd_ut {
214 jd if jd < JulianDate::new(2_067_314.5) => delta_t_ancient(jd),
215 jd if jd < JulianDate::new(2_305_447.5) => delta_t_medieval(jd),
216 jd if jd < JD_1992 => delta_t_table(jd),
217 jd if jd < JD_2026 => delta_t_observed(jd),
218 _ => delta_t_extrapolated(jd_ut),
219 }
220}
221
222impl Time<UT> {
225 #[inline]
230 pub fn delta_t(&self) -> Seconds {
231 delta_t_seconds_from_ut(JulianDate::from_days(self.quantity()))
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use qtty::{Day, Days};
239
240 #[test]
241 fn delta_t_ancient_sample() {
242 let dt = delta_t_seconds_from_ut(JulianDate::new(2_000_000.0));
243 assert!((dt - Seconds::new(2_734.342_214_024_879_5)).abs() < Seconds::new(1e-6));
244 }
245
246 #[test]
247 fn delta_t_medieval_sample() {
248 let dt = delta_t_seconds_from_ut(JulianDate::new(2_100_000.0));
249 assert!((dt - Seconds::new(1_485.280_240_204_242_3)).abs() < Seconds::new(1e-6));
250 }
251
252 #[test]
253 fn delta_t_table_sample() {
254 let dt = delta_t_seconds_from_ut(JulianDate::new(2_312_752.5));
255 assert!((dt - Seconds::new(115.0)).abs() < Seconds::new(1e-6));
256 }
257
258 #[test]
259 fn delta_t_table_upper_clip() {
260 let dt = delta_t_table(JulianDate::new(2_449_356.0));
261 assert!((dt - Seconds::new(59.3)).abs() < Seconds::new(1e-6));
262 }
263
264 #[test]
265 fn delta_t_2000() {
266 let dt = delta_t_seconds_from_ut(JulianDate::J2000);
268 assert!(
269 (dt - Seconds::new(63.83)).abs() < Seconds::new(0.1),
270 "ΔT at J2000 = {dt}, expected 63.83 s"
271 );
272 }
273
274 #[test]
275 fn delta_t_2010() {
276 let dt = delta_t_seconds_from_ut(JulianDate::new(2_455_197.5));
279 assert!(
280 (dt - Seconds::new(66.07)).abs() < Seconds::new(0.5),
281 "ΔT at 2010. = {dt}, expected ~66.07 s"
282 );
283 }
284
285 #[test]
286 fn delta_t_2020() {
287 let dt = delta_t_seconds_from_ut(JulianDate::new(2_458_849.5));
291 assert!(
292 (dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
293 "ΔT at 2020.0 = {dt}, expected ~69.36 s"
294 );
295 }
296
297 #[test]
298 fn delta_t_2025() {
299 let dt = delta_t_seconds_from_ut(JulianDate::new(2_460_676.5));
302 assert!(
303 (dt - Seconds::new(69.36)).abs() < Seconds::new(0.5),
304 "ΔT at 2025.0 = {dt}, expected ~69.36 s"
305 );
306 }
307
308 #[test]
309 fn delta_t_extrapolated_near_future() {
310 let jd_2030 = JulianDate::new(2_462_502.5);
313 let dt = delta_t_seconds_from_ut(jd_2030);
314 assert!(
315 (dt - Seconds::new(69.44)).abs() < Seconds::new(1.0),
316 "ΔT at 2030. = {dt}, expected ~69.44 s"
317 );
318 assert!(dt < Seconds::new(75.0), "ΔT at 2030 is too large: {dt}");
320 }
321
322 #[test]
323 fn ut_scale_applies_delta_t() {
324 let ut = Time::<UT>::new(2_451_545.0);
325 let jd_tt = ut.to::<crate::JD>();
326 let offset = jd_tt - JulianDate::new(2_451_545.0);
327 let expected = delta_t_seconds_from_ut(JulianDate::new(2_451_545.0)).to::<Day>();
328 assert!((offset - expected).abs() < Days::new(1e-9));
329 }
330
331 #[test]
332 fn ut_scale_roundtrip() {
333 let jd_tt = JulianDate::new(2_451_545.0);
334 let ut: Time<UT> = jd_tt.to::<UT>();
335 let back: JulianDate = ut.to::<crate::JD>();
336 assert!((back - jd_tt).abs() < Days::new(1e-12));
337 }
338
339 #[test]
340 fn delta_t_convenience_method() {
341 let ut = Time::<UT>::new(2_451_545.0);
342 let dt = ut.delta_t();
343 assert!((dt - Seconds::new(63.83)).abs() < Seconds::new(0.5));
344 }
345}