irox_time/
julian.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Contains [`JulianDate`] and others - ways of measuring a discrete amount of days from a specific
7//! Julian [`Epoch`]
8//!
9
10use core::marker::PhantomData;
11use core::ops::{Add, AddAssign, Sub, SubAssign};
12use irox_units::units::duration::{Duration, DurationUnit};
13
14use crate::epoch::{PrimeEpoch, PRIME_EPOCH, UNIX_EPOCH, VICINTIPOCH, Y2K_EPOCH};
15use crate::{
16    epoch::{Epoch, UnixTimestamp, COMMON_ERA_EPOCH, GREGORIAN_EPOCH},
17    gregorian::Date,
18    SECONDS_IN_DAY,
19};
20
21//
22/// The Julian Epoch, 01-JAN 4713 BC (Gregorian)
23pub const JULIAN_EPOCH: JulianEpoch = JulianEpoch::new(
24    JULIAN_JD_OFFSET,
25    Epoch(Date {
26        year: -4712,
27        day_of_year: 0,
28    }),
29);
30///
31/// The Julian Date is the number of days since the [`JULIAN_EPOCH`]
32///
33/// Noon 12:00 on 01-JAN, 4713 BC
34pub type JulianDate = JulianDayNumber<JulianEpoch>;
35impl JulianDate {
36    pub const fn new(day_number: f64) -> Self {
37        Self {
38            epoch: JULIAN_EPOCH,
39            day_number,
40            _phantom: PhantomData,
41        }
42    }
43}
44
45pub const JULIAN_JD_OFFSET: f64 = 0.0_f64;
46
47pub trait AsJulianEpoch {
48    fn get_julian_epoch() -> JulianEpoch;
49}
50
51///
52/// A Julian Date represents a number of days (86400 seconds) since a particular
53/// Epoch.
54#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
55pub struct JulianDayNumber<T: AsJulianEpoch> {
56    epoch: JulianEpoch,
57    day_number: f64,
58
59    _phantom: PhantomData<T>,
60}
61
62impl<T: AsJulianEpoch> JulianDayNumber<T> {
63    pub(crate) const fn new_internal(epoch: JulianEpoch, day_number: f64) -> Self {
64        JulianDayNumber {
65            epoch,
66            day_number,
67            _phantom: PhantomData,
68        }
69    }
70    /// Returns the julian day number (number of days since the epoch) of this date
71    pub const fn get_day_number(&self) -> f64 {
72        self.day_number
73    }
74    /// Returns the epoch of this date
75    pub const fn get_julian_epoch(&self) -> JulianEpoch {
76        self.epoch
77    }
78
79    /// Converts the specified JDN into the local JDN Epoch
80    pub fn from_jdn<O: AsJulianEpoch>(other: &JulianDayNumber<O>) -> Self {
81        let jdn = other.as_julian_date();
82        let je = T::get_julian_epoch();
83        Self {
84            epoch: je,
85            day_number: jdn.day_number - je.day_offset_from_julian_epoch,
86            _phantom: PhantomData,
87        }
88    }
89
90    /// Returns the julian date equivalient of this JDN
91    pub const fn as_julian_date(&self) -> JulianDate {
92        JulianDate::new_internal(
93            JULIAN_EPOCH,
94            self.day_number + self.epoch.day_offset_from_julian_epoch,
95        )
96    }
97    /// Returns the [`ReducedJulianDate`] equivalent of this JDN
98    pub fn as_reduced_date(&self) -> ReducedJulianDate {
99        ReducedJulianDate::from_jdn(self)
100    }
101    pub fn as_modified_date(&self) -> ModifiedJulianDate {
102        ModifiedJulianDate::from_jdn(self)
103    }
104    pub fn as_truncated_date(&self) -> TruncatedJulianDate {
105        TruncatedJulianDate::from_jdn(self)
106    }
107    pub fn as_lilian_date(&self) -> LilianDate {
108        LilianDate::from_jdn(self)
109    }
110    pub fn as_rata_die_date(&self) -> RataDieDate {
111        RataDieDate::from_jdn(self)
112    }
113    pub fn as_prime_date(&self) -> PrimeDate {
114        PrimeDate::from_jdn(self)
115    }
116}
117
118/// No functionality, used as a static compile-time type check
119#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
120pub struct JulianEpoch {
121    day_offset_from_julian_epoch: f64,
122    epoch: Epoch,
123}
124impl AsJulianEpoch for JulianEpoch {
125    fn get_julian_epoch() -> JulianEpoch {
126        JULIAN_EPOCH
127    }
128}
129impl Eq for JulianEpoch {}
130impl JulianEpoch {
131    pub const fn new(day_offset_from_julian_epoch: f64, epoch: Epoch) -> Self {
132        JulianEpoch {
133            day_offset_from_julian_epoch,
134            epoch,
135        }
136    }
137    pub fn get_day_offset_from_julian_epoch(&self) -> f64 {
138        self.day_offset_from_julian_epoch
139    }
140    pub fn get_epoch(&self) -> Epoch {
141        self.epoch
142    }
143}
144
145macro_rules! impl_julian {
146    ($date:ident,$epoch:ident) => {
147        impl $date {
148            pub const fn new(day_number: f64) -> Self {
149                Self {
150                    epoch: $epoch,
151                    day_number,
152                    _phantom: PhantomData,
153                }
154            }
155        }
156        // impl From<JulianDate> for $date {
157        //     fn from(value: JulianDate) -> Self {
158        //         $date::new(value.day_number - $epoch.day_offset_from_julian_epoch)
159        //     }
160        // }
161        impl From<$date> for JulianDate {
162            fn from(value: $date) -> Self {
163                JulianDate::new(value.day_number + $epoch.day_offset_from_julian_epoch)
164            }
165        }
166        impl From<&JulianDate> for $date {
167            fn from(value: &JulianDate) -> Self {
168                $date::new(value.day_number - $epoch.day_offset_from_julian_epoch)
169            }
170        }
171        impl From<&$date> for JulianDate {
172            fn from(value: &$date) -> Self {
173                JulianDate::new(value.day_number + $epoch.day_offset_from_julian_epoch)
174            }
175        }
176    };
177}
178
179/// No functionality, used as a static compile-time type check
180#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
181pub struct ReducedJulianEpoch;
182impl AsJulianEpoch for ReducedJulianEpoch {
183    fn get_julian_epoch() -> JulianEpoch {
184        REDUCED_JULIAN_EPOCH
185    }
186}
187///
188/// The Reduced Julian Epoch, 16-NOV-1858
189///
190/// 2400000 JD after the [`JULIAN_EPOCH`]
191pub const REDUCED_JULIAN_EPOCH: JulianEpoch = JulianEpoch::new(
192    REDUCED_JD_OFFSET,
193    Epoch(Date {
194        year: 1858,
195        day_of_year: 320,
196    }),
197);
198
199///
200/// The Reduced Julian Date is the number of days since the [`REDUCED_JULIAN_EPOCH`]
201/// or 2400000 days after the [`JULIAN_EPOCH`]
202///
203/// Noon 12:00 on 16-NOV-1858
204pub type ReducedJulianDate = JulianDayNumber<ReducedJulianEpoch>;
205
206/// The offset from the [`JULIAN_EPOCH`] for the [`ReducedJulianDate`]
207pub const REDUCED_JD_OFFSET: f64 = 2400000_f64;
208impl_julian!(ReducedJulianDate, REDUCED_JULIAN_EPOCH);
209
210/// No functionality, used as a static compile-time type check
211#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
212pub struct ModifiedJulianEpoch;
213impl AsJulianEpoch for ModifiedJulianEpoch {
214    fn get_julian_epoch() -> JulianEpoch {
215        MODIFIED_JULIAN_EPOCH
216    }
217}
218
219pub const MODIFIED_JULIAN_EPOCH: JulianEpoch = JulianEpoch::new(
220    MODIFIED_JD_OFFSET,
221    Epoch(Date {
222        year: 1858,
223        day_of_year: 320,
224    }),
225);
226
227///
228/// The Modified Julian Date shifts the Reduced Julian Date by 12 hours forward,
229/// or 2400000.5 days after the [`JULIAN_EPOCH`]
230///
231/// Midnight on 17-NOV-1858
232pub type ModifiedJulianDate = JulianDayNumber<ModifiedJulianEpoch>;
233
234/// The offset from the [`JULIAN_EPOCH`] for the [`ModifiedJulianDate`]
235pub const MODIFIED_JD_OFFSET: f64 = 2400000.5_f64;
236impl_julian!(ModifiedJulianDate, MODIFIED_JULIAN_EPOCH);
237
238/// No functionality, used as a static compile-time type check
239#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
240pub struct TruncatedJulianEpoch;
241impl AsJulianEpoch for TruncatedJulianEpoch {
242    fn get_julian_epoch() -> JulianEpoch {
243        TRUNCATED_JULIAN_EPOCH
244    }
245}
246///
247/// The Truncated Julian Epoch, used by NASA, 24-MAY-1968
248///
249/// 2440000.5 JD after the [`JULIAN_EPOCH`]
250pub const TRUNCATED_JULIAN_EPOCH: JulianEpoch = JulianEpoch::new(
251    TRUNCATED_JD_OFFSET,
252    Epoch(Date {
253        year: 1968,
254        day_of_year: 145,
255    }),
256);
257
258///
259/// The Truncated Julian Date uses the [`TRUNCATED_JULIAN_EPOCH`] as a round
260/// offset of 2440000.5 after the [`JULIAN_EPOCH`]
261///
262/// Midnight on 24-MAY-1968
263pub type TruncatedJulianDate = JulianDayNumber<TruncatedJulianEpoch>;
264
265/// The offset from the [`JULIAN_EPOCH`] for the [`TruncatedJulianDate`]
266pub const TRUNCATED_JD_OFFSET: f64 = 2440000.5_f64;
267impl_julian!(TruncatedJulianDate, TRUNCATED_JULIAN_EPOCH);
268
269/// No functionality, used as a static compile-time type check
270#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
271pub struct LilianEpoch;
272impl AsJulianEpoch for LilianEpoch {
273    fn get_julian_epoch() -> JulianEpoch {
274        LILIAN_EPOCH
275    }
276}
277
278///
279/// The Lilian Date is the day number offset from the [`GREGORIAN_EPOCH`],
280/// 2299159.5 JD after the [`JULIAN_EPOCH`]
281///
282/// Midnight on 15-OCT-1582
283pub type LilianDate = JulianDayNumber<LilianEpoch>;
284
285/// The offset from the [`JULIAN_EPOCH`] for the [`LilianDate`]
286pub const LILIAN_JD_OFFSET: f64 = 2299159.5_f64;
287
288pub const LILIAN_EPOCH: JulianEpoch = JulianEpoch::new(LILIAN_JD_OFFSET, GREGORIAN_EPOCH);
289impl_julian!(LilianDate, LILIAN_EPOCH);
290
291/// No functionality, used as a static compile-time type check
292#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
293pub struct RataDieEpoch;
294impl AsJulianEpoch for RataDieEpoch {
295    fn get_julian_epoch() -> JulianEpoch {
296        RATA_DIE_EPOCH
297    }
298}
299
300///
301/// The Rata Die (Latin: "Fixed Date") is the fixed number of days in the Common
302/// Era, since Midnight 01-01-0001 AD, 1721424.5 after [`JULIAN_EPOCH`]
303pub type RataDieDate = JulianDayNumber<RataDieEpoch>;
304
305/// The offset from the [`JULIAN_EPOCH`] for the [`RataDieDate`]
306pub const RATA_DIE_JD_OFFSET: f64 = 1721424.5_f64;
307pub const RATA_DIE_EPOCH: JulianEpoch = JulianEpoch::new(RATA_DIE_JD_OFFSET, COMMON_ERA_EPOCH);
308impl_julian!(RataDieDate, RATA_DIE_EPOCH);
309
310/// The offset from the [`JULIAN_EPOCH`] for the [`UnixTimestamp`]
311pub const UNIX_TS_JD_OFFSET: f64 = 2240587.5_f64;
312pub const UNIX_JD_EPOCH: JulianEpoch = JulianEpoch::new(UNIX_TS_JD_OFFSET, UNIX_EPOCH);
313
314///
315/// The Prime Date is the fixed number of days since 01-JAN-1900.
316pub type PrimeDate = JulianDayNumber<PrimeEpoch>;
317pub const PRIME_JD_OFFSET: f64 = 2415020.5_f64;
318pub const PRIME_JD_EPOCH: JulianEpoch = JulianEpoch::new(PRIME_JD_OFFSET, PRIME_EPOCH);
319impl AsJulianEpoch for PrimeEpoch {
320    fn get_julian_epoch() -> JulianEpoch {
321        PRIME_JD_EPOCH
322    }
323}
324impl_julian!(PrimeDate, PRIME_JD_EPOCH);
325
326pub const MJD2000_OFFSET: f64 = 2451544.5;
327pub const MJD2000_EPOCH: JulianEpoch = JulianEpoch::new(MJD2000_OFFSET, Y2K_EPOCH);
328pub struct MJD2000Epoch;
329impl AsJulianEpoch for MJD2000Epoch {
330    fn get_julian_epoch() -> JulianEpoch {
331        MJD2000_EPOCH
332    }
333}
334pub type MJD2000 = JulianDayNumber<MJD2000Epoch>;
335impl_julian!(MJD2000, MJD2000_EPOCH);
336
337pub const VICINTI_OFFSET: f64 = 2458849.5;
338pub const VICINTI_JD_EPOCH: JulianEpoch = JulianEpoch::new(VICINTI_OFFSET, VICINTIPOCH);
339
340impl<T: AsJulianEpoch> Add<Duration> for JulianDayNumber<T> {
341    type Output = JulianDayNumber<T>;
342
343    fn add(self, rhs: Duration) -> Self::Output {
344        let day_number = self.day_number + rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
345        Self::new_internal(self.epoch, day_number)
346    }
347}
348
349impl<T: AsJulianEpoch> Add<&Duration> for JulianDayNumber<T> {
350    type Output = JulianDayNumber<T>;
351
352    fn add(self, rhs: &Duration) -> Self::Output {
353        let day_number = self.day_number + rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
354        Self::new_internal(self.epoch, day_number)
355    }
356}
357
358impl<T: AsJulianEpoch> Sub<Duration> for JulianDayNumber<T> {
359    type Output = JulianDayNumber<T>;
360
361    fn sub(self, rhs: Duration) -> Self::Output {
362        let day_number = self.day_number - rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
363        Self::new_internal(self.epoch, day_number)
364    }
365}
366
367impl<T: AsJulianEpoch> Sub<&Duration> for JulianDayNumber<T> {
368    type Output = JulianDayNumber<T>;
369
370    fn sub(self, rhs: &Duration) -> Self::Output {
371        let day_number = self.day_number - rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
372        Self::new_internal(self.epoch, day_number)
373    }
374}
375
376impl<T: AsJulianEpoch> AddAssign<Duration> for JulianDayNumber<T> {
377    fn add_assign(&mut self, rhs: Duration) {
378        self.day_number += rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
379    }
380}
381
382impl<T: AsJulianEpoch> AddAssign<&Duration> for JulianDayNumber<T> {
383    fn add_assign(&mut self, rhs: &Duration) {
384        self.day_number += rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
385    }
386}
387
388impl<T: AsJulianEpoch> SubAssign<Duration> for JulianDayNumber<T> {
389    fn sub_assign(&mut self, rhs: Duration) {
390        self.day_number -= rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
391    }
392}
393
394impl<T: AsJulianEpoch> SubAssign<&Duration> for JulianDayNumber<T> {
395    fn sub_assign(&mut self, rhs: &Duration) {
396        self.day_number -= rhs.as_seconds_f64() / SECONDS_IN_DAY as f64;
397    }
398}
399
400impl<T: AsJulianEpoch> Sub<JulianDayNumber<T>> for JulianDayNumber<T> {
401    type Output = Duration;
402
403    fn sub(self, rhs: JulianDayNumber<T>) -> Self::Output {
404        let dx = self.day_number - rhs.day_number;
405        Duration::new(dx, DurationUnit::Day)
406    }
407}
408impl<T: AsJulianEpoch> Sub<&JulianDayNumber<T>> for JulianDayNumber<T> {
409    type Output = Duration;
410
411    fn sub(self, rhs: &JulianDayNumber<T>) -> Self::Output {
412        let dx = self.day_number - rhs.day_number;
413        Duration::new(dx, DurationUnit::Day)
414    }
415}
416
417impl From<UnixTimestamp> for JulianDate {
418    fn from(value: UnixTimestamp) -> Self {
419        let jd = value.get_offset().as_seconds_f64() / SECONDS_IN_DAY as f64 + UNIX_TS_JD_OFFSET;
420        JulianDate::new(jd)
421    }
422}
423
424impl From<JulianDate> for UnixTimestamp {
425    fn from(value: JulianDate) -> Self {
426        let ts = (value.day_number - UNIX_TS_JD_OFFSET) * SECONDS_IN_DAY as f64;
427        UnixTimestamp::from_seconds_f64(ts)
428    }
429}