Skip to main content

deep_time/dt/
conveniences.rs

1use crate::ATTOS_PER_WEEK;
2use crate::{ATTOS_PER_SEC_I128, SEC_PER_DAYI64};
3use crate::{Dt, Real, Scale};
4
5impl Dt {
6    /// Returns this [`Dt`] but as a unix timestamp where the:
7    /// - `.sec` field is seconds since the UNIX epoch (1970-01-01 00:00:00).
8    /// - `.attos` field is remaining fractional seconds.
9    ///
10    /// ## Notes:
11    ///
12    /// - Assumes this [`Dt`] is from the 2000-01-01 noon epoch.
13    #[inline]
14    pub const fn to_unix(&self, current: Scale, new: Scale) -> Dt {
15        self.to(current, new)
16            .to_diff_raw(Dt::UNIX_EPOCH.to_internal(new))
17    }
18
19    /// Creates a TAI [`Dt`] from a unix (1970 epoch) timestamp.
20    #[inline]
21    pub const fn from_unix(unix: Real, current: Scale) -> Dt {
22        Self::from_diff_and_scale(Self::from_sec_f(unix), Dt::UNIX_EPOCH, current)
23    }
24
25    /// Returns this [`Dt`] but as an ntp timestamp where the:
26    ///
27    /// - `.sec` field is seconds since the epoch 1900-01-01 00:00:00 UTC.
28    /// - `.attos` field is remaining fractional seconds.
29    ///
30    /// ## Notes:
31    ///
32    /// - Assumes this [`Dt`] is from the 2000-01-01 noon epoch.
33    /// - NTP is on an SI continuous time scale. Not UTC.
34    ///
35    /// ## Example:
36    ///
37    /// ```
38    /// use deep_time::{Dt, Scale};
39    ///
40    /// // 2698012800
41    /// let dt = Dt::from_ymd_on(1985, 7, 1, Scale::TAI);
42    /// let ntp = dt.to_ntp(Scale::TAI, Scale::TAI);
43    /// assert_eq!(
44    ///     ntp.sec, 2698012800_i64,
45    ///     "ntp sec for 1985 is wrong, got: {}, expected: {}",
46    ///     ntp.sec, 2698012800_i64
47    /// );
48    /// let dt2 = Dt::from_ntp(ntp.to_sec_f(), Scale::TAI);
49    /// assert_eq!(
50    ///     dt.sec, dt2.sec,
51    ///     "round trip to Dt got wrong sec, old: {}, new: {}",
52    ///     dt.sec, dt2.sec
53    /// );
54    /// let ymd = dt2.to_ymdhms_on(Scale::TAI, Scale::TAI);
55    /// assert_eq!(ymd.yr, 1985_i64);
56    /// assert_eq!(ymd.mo, 7);
57    /// assert_eq!(ymd.day, 1);
58    /// assert_eq!(ymd.hr, 0);
59    /// assert_eq!(ymd.min, 0);
60    /// assert_eq!(ymd.sec, 0);
61    /// assert_eq!(ymd.attos, 0);
62    /// ```
63    #[inline]
64    pub const fn to_ntp(&self, current: Scale, new: Scale) -> Dt {
65        self.to(current, new)
66            .to_diff_raw(Dt::NTP_EPOCH.to_internal(new))
67    }
68
69    /// Creates a TAI [`Dt`] from an ntp (1900 epoch) timestamp.
70    #[inline]
71    pub const fn from_ntp(ntp: Real, current: Scale) -> Dt {
72        Self::from_diff_and_scale(Self::from_sec_f(ntp), Dt::NTP_EPOCH, current)
73    }
74
75    /// Returns the GPS week number and the exact Time of Week (TOW) for this instant
76    /// when expressed in **GPS Time**.
77    ///
78    /// - GPS Time is continuous (no leap seconds) and starts at the
79    ///   [`Dt::GPS_EPOCH`] (1980-01-06 00:00:00 UTC).
80    /// - The returned TOW is a full-precision `Dt` (attosecond resolution) on the
81    ///   TAI scale.
82    ///
83    /// This is the most precise way to obtain GPS week + TOW information.
84    pub const fn to_gps_wk_and_tow(&self, current: Scale) -> (i64, Dt) {
85        let total_attos = self.to_gps(current).to_attos();
86
87        let wk = total_attos.div_euclid(ATTOS_PER_WEEK) as i64;
88        let tow_attos = total_attos.rem_euclid(ATTOS_PER_WEEK);
89
90        (wk, Dt::from_attos(tow_attos, Scale::TAI))
91    }
92
93    /// Creates a `Dt` in GPS Time (GPS) from a GPS week number and
94    /// Time of Week (TOW).
95    ///
96    /// This is the exact inverse of [`Self::to_gps_week_and_tow`].
97    ///
98    /// - `week`: Full GPS week number (can be negative for dates before 1980).
99    /// - `tow`: Time of Week as a [`Dt`]. Values ≥ 604800 seconds are
100    ///   automatically carried into the week number.
101    ///
102    /// The resulting `Dt` is always in `Scale::GPS`.
103    #[inline]
104    pub const fn from_gps_wk_and_tow(wk: i64, tow: Dt) -> Self {
105        let total_attos = (wk as i128) * ATTOS_PER_WEEK + tow.to_attos();
106        Self::GPS_EPOCH.add(Dt::from_attos(total_attos, Scale::TAI))
107    }
108
109    /// Creates a `Dt` in GPS Time from a GPS week number and
110    /// floating-point Time of Week.
111    ///
112    /// This is the floating-point counterpart to [`Self::from_gps_wk_and_tow`].
113    #[inline]
114    pub const fn from_gps_wk_and_tow_f(week: i64, tow: Real) -> Self {
115        let tow_span = Dt::from_sec_f(tow);
116        Self::from_gps_wk_and_tow(week, tow_span)
117    }
118
119    /// Returns the elapsed time since the GPS epoch as a [`Dt`] on the GPS scale.
120    ///
121    /// The GPS epoch is [`Dt::GPS_EPOCH`].
122    #[inline]
123    pub const fn to_gps(&self, current: Scale) -> Dt {
124        self.to(current, Scale::GPS)
125            .to_diff_raw(Dt::GPS_EPOCH.to(Scale::TAI, Scale::GPS))
126    }
127
128    /// Inverse of [`Self::to_gps`].
129    pub const fn from_gps(elapsed: Dt) -> Self {
130        Self::GPS_EPOCH.add(elapsed)
131    }
132
133    /// Floating-point version of [`Self::from_gps`].
134    #[inline]
135    pub const fn from_gps_f(elapsed_sec: Real) -> Self {
136        Self::from_gps(Dt::from_sec_f(elapsed_sec))
137    }
138
139    /// Returns the day of the GPS week (0 = Sunday, 1 = Monday, …, 6 = Saturday).
140    ///
141    /// This value is computed directly from the GPS Time of Week and is
142    /// independent of the Gregorian calendar or civil time.
143    pub const fn to_gps_day_of_wk(&self, current: Scale) -> u8 {
144        let (_, tow) = self.to_gps_wk_and_tow(current);
145        let secs = tow.to_attos() / ATTOS_PER_SEC_I128;
146
147        (secs / SEC_PER_DAYI64 as i128) as u8
148    }
149
150    /// Returns the Time of Week (TOW) as a floating-point value in seconds.
151    ///
152    /// This is a convenience method for code that prefers `f64` / `Real`.
153    /// For full attosecond precision use [`Self::to_gps_wk_and_tow`].
154    #[inline]
155    pub const fn to_gps_tow_f(&self, current: Scale) -> Real {
156        let (_, tow) = self.to_gps_wk_and_tow(current);
157        tow.to_sec_f()
158    }
159
160    /// Returns only the GPS week number.
161    ///
162    /// Convenience method. For both the week number and the Time of Week, use
163    /// [`Self::to_gps_wk_and_tow`].
164    #[inline]
165    pub const fn to_gps_wk(&self, current: Scale) -> i64 {
166        self.to_gps_wk_and_tow(current).0
167    }
168
169    /// Returns the elapsed time since the Chandra X-ray Center (CXC) epoch
170    /// as a [`Dt`] on the TT scale.
171    ///
172    /// The CXC epoch is [`Dt::CXC_EPOCH`].
173    #[inline]
174    pub const fn to_cxcsec(&self, current: Scale) -> Dt {
175        self.to(current, Scale::TT)
176            .to_diff_raw(Dt::CXC_EPOCH.to(Scale::TAI, Scale::TT))
177    }
178
179    /// Inverse of [`Self::to_cxcsec`].
180    pub const fn from_cxcsec(elapsed: Dt) -> Self {
181        Self::CXC_EPOCH.add(elapsed)
182    }
183
184    /// Floating-point counterpart of [`Self::from_cxcsec`].
185    #[inline]
186    pub const fn from_cxcsec_f(elapsed_sec: Real) -> Self {
187        Self::from_cxcsec(Dt::from_sec_f(elapsed_sec))
188    }
189
190    /// Returns the elapsed time since the GPS/Galileo Experiment (GALEX) epoch
191    /// as a [`Dt`] on the TAI scale.
192    ///
193    /// The GALEX epoch is [`Self::GPS_EPOCH`].
194    #[inline]
195    pub const fn to_galexsec(&self, current: Scale) -> Dt {
196        self.to(current, Scale::UTC)
197            .to_diff_raw(Dt::GPS_EPOCH.to(Scale::TAI, Scale::UTC))
198    }
199
200    /// Inverse of [`Self::to_galexsec`].
201    pub const fn from_galexsec(elapsed: Dt) -> Self {
202        let epoch_utc = Self::GPS_EPOCH.to(Scale::TAI, Scale::UTC);
203        epoch_utc.add(elapsed).to(Scale::UTC, Scale::TAI)
204    }
205
206    /// Floating-point counterpart of [`Self::from_galexsec`].
207    #[inline]
208    pub const fn from_galexsec_f(elapsed_sec: Real) -> Self {
209        Self::from_galexsec(Dt::from_sec_f(elapsed_sec))
210    }
211}