irox_time/
epoch.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Contains the concept of an [`Epoch`] - a specific Proleptic Gregorian [`Date`] from which a
7//! [`Timestamp`] is measured against.
8//!
9//! A [`Timestamp`] is a [`Duration`], a physical amount of time measured against an [`Epoch`]
10//!
11//! Epochs
12//! -------
13//! | Epoch     | JDN         | Year    | DOY   | Julian Epoch               | Gregorian Epoch               | Timestamp              |
14//! |-----------|-------------|---------|-------|----------------------------|-------------------------------|------------------------|
15//! | Julian    | `0.0`       | `-4712` | `0`   | [`JULIAN_EPOCH`]           |                               |
16//! | Rata Die  | `1721424.5` | `0001`  | `0`   | [`RATA_DIE_EPOCH`]         | [`COMMON_ERA_EPOCH`]          |
17//! | Lilian    | `2299159.5` | `1582`  | `257` | [`LILIAN_EPOCH`]           | [`GREGORIAN_EPOCH`]           | [`GregorianTimestamp`] |
18//! | Windows   | `2305813.5` | `1601`  | `0`   |                            | [`WINDOWS_NT_EPOCH`]          | [`WindowsNTTimestamp`] |
19//! | Reduced   | `2400000`   | `1858`  | `320` | [`REDUCED_JULIAN_EPOCH`]   |                               |
20//! | Modified  | `2400000.5` | `1858`  | `320` | [`MODIFIED_JULIAN_EPOCH`]  |                               |
21//! | Prime     | `2415020.5` | `1900`  | `0`   | [`PRIME_JD_EPOCH`]         | [`PRIME_EPOCH`] [`NTP_EPOCH`] | [`PrimeTimestamp`]     |
22//! | Truncated | `2440000.5` | `1968`  | `145` | [`TRUNCATED_JULIAN_EPOCH`] |                               |
23//! | Unix      | `2440587.5` | `1970`  | `0`   | [`UNIX_JD_EPOCH`]          | [`UNIX_EPOCH`]                | [`UnixTimestamp`]      |
24//! | GPS       | `2444244.5` | `1980`  | `5`   |                            | [`GPS_EPOCH`]                 | [`GPSTimestamp`]       |
25//! | MJD2000   | `2451544.5` | `2000`  | `0`   | [`MJD2000_EPOCH`]          | [`Y2K_EPOCH`]                 |
26//! | Leapoch   | `2451604.5` | `2000`  | `60`  |                            | [`LEAPOCH`]                   | [`LeapochTimestamp`]   |
27//! | Vicinti   | `2458849.5` | `2020`  | `0`   | [`VICINTI_JD_EPOCH`]       | [`VICINTIPOCH`]               | [`VicintiTimestamp`]   |
28
29use core::cmp::Ordering;
30use core::marker::PhantomData;
31use core::ops::{Add, AddAssign, Sub, SubAssign};
32use irox_tools::cfg_docs;
33use irox_units::units::duration::Duration;
34
35use crate::gregorian::Date;
36use crate::julian::JulianDate;
37
38cfg_docs! {
39    use crate::julian::{JULIAN_EPOCH, RATA_DIE_EPOCH, LILIAN_EPOCH, REDUCED_JULIAN_EPOCH, MODIFIED_JULIAN_EPOCH, PRIME_JD_EPOCH, TRUNCATED_JULIAN_EPOCH, UNIX_JD_EPOCH, MJD2000_EPOCH, VICINTI_JD_EPOCH};
40}
41
42///
43/// An `Epoch` serves as a reference point from which time is measured.
44#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
45pub struct Epoch(pub Date);
46
47impl Epoch {
48    ///
49    /// The Gregorian Date of this particular Epoch.
50    pub fn get_gregorian_date(&self) -> Date {
51        self.0
52    }
53}
54
55///
56/// Represents a [`Duration`] offset from a particular [`Epoch`]
57#[derive(Debug, Copy, Clone)]
58pub struct Timestamp<T> {
59    epoch: Epoch,
60    offset: Duration,
61
62    _phantom: PhantomData<T>,
63}
64
65impl<T> Timestamp<T> {
66    pub const fn new(epoch: Epoch, duration: Duration) -> Self {
67        Self {
68            epoch,
69            offset: duration,
70            _phantom: PhantomData,
71        }
72    }
73
74    ///
75    /// Returns the base epoch for this timestamp
76    #[must_use]
77    pub const fn get_epoch(&self) -> Epoch {
78        self.epoch
79    }
80
81    ///
82    /// Returns the relative offset of this timestamp from the specified epoch.
83    #[must_use]
84    pub const fn get_offset(&self) -> Duration {
85        self.offset
86    }
87}
88impl<T> PartialEq for Timestamp<T> {
89    fn eq(&self, other: &Self) -> bool {
90        if self.epoch != other.epoch {
91            return false;
92        }
93        self.offset.eq(&other.offset)
94    }
95}
96impl<T> Eq for Timestamp<T> {}
97
98impl<T> PartialOrd for Timestamp<T> {
99    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
100        Some(self.offset.cmp(&other.offset))
101    }
102}
103impl<T> Ord for Timestamp<T> {
104    fn cmp(&self, other: &Self) -> Ordering {
105        self.offset.cmp(&other.offset)
106    }
107}
108
109///
110/// The Unix Epoch, 1970-01-01, 00:00:00
111pub const UNIX_EPOCH: Epoch = Epoch(Date {
112    year: 1970,
113    day_of_year: 0,
114});
115
116///
117/// Represents a duration offset from the [`UNIX_EPOCH`].
118pub type UnixTimestamp = Timestamp<UnixEpoch>;
119
120/// `UnixEpoch` is a compile-time check for [`UnixTimestamp`] = [`Timestamp<UnixEpoch>`]
121#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
122pub struct UnixEpoch;
123
124pub trait FromTimestamp<T> {
125    fn from_timestamp(other: &Timestamp<T>) -> Self;
126}
127
128macro_rules! derive_timestamp_impl {
129    ($epoch:ident,$name:ident) => {
130        impl $name {
131            ///
132            /// Creates a new timestamp given the specified offset
133            pub const fn from_offset(offset: Duration) -> $name {
134                $name::new($epoch, offset)
135            }
136
137            ///
138            /// Creates a new timestamp given the specified number of seconds
139            pub const fn from_seconds(seconds: u32) -> $name {
140                $name::from_seconds_f64(seconds as f64)
141            }
142
143            ///
144            /// Creates a new timestamp given the specified number of fractional seconds
145            pub const fn from_seconds_f64(seconds: f64) -> $name {
146                $name::from_offset(Duration::new_seconds(seconds))
147            }
148        }
149        impl Default for $name {
150            fn default() -> Self {
151                $name::from_offset(Duration::default())
152            }
153        }
154        impl From<Duration> for $name {
155            fn from(value: Duration) -> Self {
156                $name::from_offset(value)
157            }
158        }
159
160        impl<T> FromTimestamp<T> for $name {
161            fn from_timestamp(other: &Timestamp<T>) -> Self {
162                let epoch_offset = $epoch.0 - other.epoch.0;
163                let new_duration = other.offset - epoch_offset;
164                $name::from_offset(new_duration)
165            }
166        }
167    };
168}
169
170impl<T> AddAssign<Duration> for Timestamp<T> {
171    fn add_assign(&mut self, rhs: Duration) {
172        self.offset += rhs;
173    }
174}
175
176impl<T> SubAssign<Duration> for Timestamp<T> {
177    fn sub_assign(&mut self, rhs: Duration) {
178        self.offset -= rhs;
179    }
180}
181
182impl<T> AddAssign<&Duration> for Timestamp<T> {
183    fn add_assign(&mut self, rhs: &Duration) {
184        self.offset += *rhs;
185    }
186}
187
188impl<T> SubAssign<&Duration> for Timestamp<T> {
189    fn sub_assign(&mut self, rhs: &Duration) {
190        self.offset -= *rhs;
191    }
192}
193impl<T> SubAssign<&mut Duration> for Timestamp<T> {
194    fn sub_assign(&mut self, rhs: &mut Duration) {
195        self.offset -= *rhs;
196    }
197}
198macro_rules! impl_sub_timestamp {
199    ($($sub:ty)+, $($slf:ty)+) => {
200        impl<T> Sub<$($sub)+> for $($slf)+ {
201            type Output = Duration;
202
203            fn sub(self, rhs: $($sub)+) -> Self::Output {
204                self.offset - rhs.offset
205            }
206        }
207    };
208}
209macro_rules! impl_sub_duration {
210    ($($sub:ty)+, $($slf:ty)+) => {
211        impl<T> Sub<$($sub)+> for $($slf)+ {
212            type Output = Timestamp<T>;
213
214            fn sub(self, rhs: $($sub)+) -> Self::Output {
215                let offset = self.offset - rhs;
216                Timestamp::new(self.epoch, offset)
217            }
218        }
219    };
220}
221macro_rules! impl_add_timestamp {
222    ($($sub:ty)+, $($slf:ty)+) => {
223        impl<T> Add<$($sub)+> for $($slf)+ {
224            type Output = Timestamp<T>;
225
226            fn add(self, rhs: $($sub)+) -> Self::Output {
227                let offset = self.offset + rhs;
228                Timestamp::new(self.epoch, offset)
229            }
230        }
231    };
232}
233macro_rules! impl_op {
234    ($op:ident, $($operand:ty)+) => {
235        $op!($($operand)+, Timestamp<T>);
236        $op!($($operand)+, &Timestamp<T>);
237        $op!($($operand)+, &mut Timestamp<T>);
238        $op!(&$($operand)+, Timestamp<T>);
239        $op!(&$($operand)+, &Timestamp<T>);
240        $op!(&$($operand)+, &mut Timestamp<T>);
241        $op!(&mut $($operand)+, Timestamp<T>);
242        $op!(&mut $($operand)+, &Timestamp<T>);
243        $op!(&mut $($operand)+, &mut Timestamp<T>);
244    };
245}
246impl_op!(impl_sub_timestamp, Timestamp<T>);
247impl_op!(impl_add_timestamp, Duration);
248impl_op!(impl_sub_duration, Duration);
249
250impl UnixTimestamp {
251    ///
252    /// Returns the local system clock equivalent of the unix timestamp
253    #[must_use]
254    #[cfg(feature = "std")]
255    pub fn now() -> UnixTimestamp {
256        match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
257            Ok(t) => UnixTimestamp::from_offset(t.into()),
258            Err(t) => {
259                UnixTimestamp::from_offset(Duration::new_seconds(-1.0 * t.duration().as_secs_f64()))
260            }
261        }
262    }
263
264    ///
265    /// Returns the local system clock duration since the timestamp.  MAY BE NEGATIVE if the clock
266    /// has changed since the last call.
267    #[must_use]
268    #[cfg(feature = "std")]
269    pub fn elapsed(&self) -> Duration {
270        Self::now().offset - self.offset
271    }
272
273    ///
274    /// Returns this timestamp as a Date
275    #[must_use]
276    pub fn as_date(&self) -> Date {
277        self.into()
278    }
279
280    #[must_use]
281    pub fn as_julian(&self) -> JulianDate {
282        (*self).into()
283    }
284}
285derive_timestamp_impl!(UNIX_EPOCH, UnixTimestamp);
286
287///
288/// The GPS Epoch, 1980-01-06, 00:00:00
289pub const GPS_EPOCH: Epoch = Epoch(Date {
290    year: 1980,
291    day_of_year: 5,
292});
293
294///
295/// Represents a duration offset from the [`GPS_EPOCH`]
296pub type GPSTimestamp = Timestamp<GPSEpoch>;
297
298/// `GPSEpoch` is a compile-time check for [`GPSTimestamp`] = [`Timestamp<GPSEpoch>`]
299#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
300pub struct GPSEpoch;
301derive_timestamp_impl!(GPS_EPOCH, GPSTimestamp);
302
303///
304/// The Gregorian Epoch, 15-OCT-1582
305pub const GREGORIAN_EPOCH: Epoch = Epoch(Date {
306    year: 1582,
307    day_of_year: 287,
308});
309
310///
311/// Represents a duration offset from the [`GREGORIAN_EPOCH`]
312pub type GregorianTimestamp = Timestamp<GregorianEpoch>;
313
314/// `GregorianEpoch` is a compile-time check for [`GregorianTimestamp`] = [`Timestamp<GregorianEpoch>`]
315#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
316pub struct GregorianEpoch;
317derive_timestamp_impl!(GREGORIAN_EPOCH, GregorianTimestamp);
318
319///
320/// The Windows NT Epoch, 01-JAN-1601.
321///
322/// Why this date?  The Gregorian calendar operates on a 400-year cycle, and
323/// 1601 is the first year of the cycle that was active at the time Windows NT
324/// was being designed. In other words, it was chosen to make the math come out
325/// nicely.
326pub const WINDOWS_NT_EPOCH: Epoch = Epoch(Date {
327    year: 1601,
328    day_of_year: 0,
329});
330
331///
332/// Represents a duration offset from the [`WINDOWS_NT_EPOCH`]
333///
334/// Note: when a duration is actually retrieved from the windows FILETIME
335/// routines, it comes back in 100-nanosecond increments from this epoch.
336pub type WindowsNTTimestamp = Timestamp<WindowsEpoch>;
337
338/// `WindowsEpoch` is a compile-time check for [`WindowsNTTimestamp`] = [`Timestamp<WindowsEpoch>`]
339#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
340pub struct WindowsEpoch;
341derive_timestamp_impl!(WINDOWS_NT_EPOCH, WindowsNTTimestamp);
342
343///
344/// The Common Era Epoch, 01-JAN-0001 AD
345pub const COMMON_ERA_EPOCH: Epoch = Epoch(Date {
346    year: 1,
347    day_of_year: 0,
348});
349
350///
351/// The Prime Epoch, 01-JAN-1900
352pub const PRIME_EPOCH: Epoch = Epoch(Date {
353    year: 1900,
354    day_of_year: 0,
355});
356///
357/// Represents a duration offset from the [`WINDOWS_NT_EPOCH`]
358///
359/// Note: when a duration is actually retrieved from the windows FILETIME
360/// routines, it comes back in 100-nanosecond increments from this epoch.
361pub type PrimeTimestamp = Timestamp<PrimeEpoch>;
362/// `PrimeEpoch` is a compile-time check for [`PrimeTimestamp`] = [`Timestamp<crate::epoch::PrimeEpoch>`]
363#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
364pub struct PrimeEpoch;
365derive_timestamp_impl!(PRIME_EPOCH, PrimeTimestamp);
366///
367/// The NTP epoch is the same as the [`PRIME_EPOCH`]
368pub const NTP_EPOCH: Epoch = PRIME_EPOCH;
369
370/// 01-MAR-2000, a mod400 year after the leap day.
371pub const LEAPOCH_TIMESTAMP: UnixTimestamp = UnixTimestamp::from_seconds(951868800);
372/// 01-MAR-2000, a mod400 year after the leap day.
373pub const LEAPOCH: Epoch = Epoch(Date {
374    year: 2000,
375    day_of_year: 60,
376});
377/// `Leapoch` is a compile-time check for [`LeapochTimestamp`] = [`Timestamp<crate::epoch::Leapoch>`]
378#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
379pub struct Leapoch;
380
381///
382/// Represents a duration offset from the [`LEAPOCH`]
383pub type LeapochTimestamp = Timestamp<Leapoch>;
384derive_timestamp_impl!(LEAPOCH, LeapochTimestamp);
385
386pub const JULIAN_DAY_1_JAN_YR0: f64 = 1721059.5;
387
388/// The Year 2000 (Y2K) Epoch 01-JAN-2000
389pub const Y2K_EPOCH: Epoch = Epoch(Date {
390    year: 2000,
391    day_of_year: 0,
392});
393
394/// The Vigintipoch (Viginti-poch, 'twenty') 01-JAN-2020
395pub const VICINTIPOCH: Epoch = Epoch(Date {
396    year: 2020,
397    day_of_year: 0,
398});
399/// `Vicintipoch` is a compile-time check for [`VicintiTimestamp`] = [`Timestamp<crate::epoch::Vicintipoch>`]
400#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
401pub struct Vicintipoch;
402pub type VicintiTimestamp = Timestamp<Vicintipoch>;
403derive_timestamp_impl!(VICINTIPOCH, VicintiTimestamp);