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    ///
34    /// ## Example:
35    ///
36    /// ```
37    /// use deep_time::{Dt, Scale};
38    ///
39    /// // 2698012800
40    /// let dt = Dt::from_ymd_on(1985, 7, 1, Scale::TAI);
41    /// let ntp = dt.to_ntp(Scale::TAI, Scale::TAI);
42    ///
43    /// assert_eq!(
44    ///     ntp.sec, 2698012800_i64,
45    ///     "ntp sec for 1985 is wrong, got: {}, expected: {}",
46    ///     ntp.sec, 2698012800_i64
47    /// );
48    ///
49    /// let dt2 = Dt::from_ntp(ntp.to_sec_f(), Scale::TAI);
50    ///
51    /// assert_eq!(
52    ///     dt.sec, dt2.sec,
53    ///     "round trip to Dt got wrong sec, old: {}, new: {}",
54    ///     dt.sec, dt2.sec
55    /// );
56    ///
57    /// let ymd = dt2.to_ymdhms_on(Scale::TAI, Scale::TAI);
58    /// assert_eq!(ymd.yr(), 1985_i64);
59    /// assert_eq!(ymd.mo(), 7);
60    /// assert_eq!(ymd.day(), 1);
61    /// assert_eq!(ymd.hr(), 0);
62    /// assert_eq!(ymd.min(), 0);
63    /// assert_eq!(ymd.sec(), 0);
64    /// assert_eq!(ymd.attos(), 0);
65    /// ```
66    #[inline]
67    pub const fn to_ntp(&self, current: Scale, new: Scale) -> Dt {
68        self.to(current, new)
69            .to_diff_raw(Dt::NTP_EPOCH.to_internal(new))
70    }
71
72    /// Creates a TAI [`Dt`] from an ntp (1900 epoch) timestamp.
73    #[inline]
74    pub const fn from_ntp(ntp: Real, current: Scale) -> Dt {
75        Self::from_diff_and_scale(Self::from_sec_f(ntp), Dt::NTP_EPOCH, current)
76    }
77
78    /// Returns the GPS week number and the exact Time of Week (TOW) for this instant
79    /// when expressed in **GPS Time**.
80    ///
81    /// - GPS Time is continuous (no leap seconds) and starts at the
82    ///   [`Dt::GPS_EPOCH`](../struct.Dt.html#associatedconstant.GPS_EPOCH)
83    ///   (1980-01-06 00:00:00 UTC).
84    /// - The returned TOW is a [`Dt`] on the TAI scale.
85    ///
86    /// This is the inverse of
87    /// [`Dt::from_gps_wk_and_tow`](../struct.Dt.html#method.from_gps_wk_and_tow).
88    ///
89    /// - `week`: Full GPS week number (can be negative for dates before 1980).
90    /// - `tow`: Time of Week as a [`Dt`]. Values ≥ 604800 seconds are
91    ///   automatically carried into the week number.
92    ///
93    /// ## Examples:
94    ///
95    /// ```
96    /// use deep_time::{Dt, Scale};
97    ///
98    /// let x = Dt::from_ymdhms_on(2000, 1, 1, 12, 0, 0, 0, Scale::TAI);
99    /// let g = x.to_gps_wk_and_tow(Scale::TAI);
100    /// let z = Dt::from_gps_wk_and_tow(g.0, g.1);
101    /// assert_eq!(x, z);
102    /// ```
103    pub const fn to_gps_wk_and_tow(&self, current: Scale) -> (i64, Dt) {
104        let total_attos = self.to_gps(current).to_attos();
105        let wk = total_attos.div_euclid(ATTOS_PER_WEEK) as i64;
106        let tow_attos = total_attos.rem_euclid(ATTOS_PER_WEEK);
107
108        (wk, Dt::from_attos(tow_attos, Scale::TAI))
109    }
110
111    /// Creates a [`Dt`] from a GPS week number and Time of Week (TOW).
112    ///
113    /// This is the inverse of
114    /// [`Dt::to_gps_wk_and_tow`](../struct.Dt.html#method.to_gps_wk_and_tow).
115    ///
116    /// - `week`: Full GPS week number (can be negative for dates before 1980).
117    /// - `tow`: Time of Week as a [`Dt`]. Values ≥ 604800 seconds are
118    ///   automatically carried into the week number.
119    ///
120    /// ## Examples:
121    ///
122    /// ```
123    /// use deep_time::{Dt, Scale};
124    ///
125    /// let x = Dt::from_ymdhms_on(2000, 1, 1, 12, 0, 0, 0, Scale::TAI);
126    /// let g = x.to_gps_wk_and_tow(Scale::TAI);
127    /// let z = Dt::from_gps_wk_and_tow(g.0, g.1);
128    /// assert_eq!(x, z);
129    /// ```
130    #[inline]
131    pub const fn from_gps_wk_and_tow(wk: i64, tow: Dt) -> Self {
132        let total_attos = (wk as i128) * ATTOS_PER_WEEK + tow.to_attos();
133        Self::GPS_EPOCH.add(Dt::from_attos(total_attos, Scale::TAI))
134    }
135
136    /// Returns the Time of Week (TOW) as a floating-point value in seconds.
137    ///
138    /// This is a convenience method for code that prefers `f64` / `Real`.
139    /// For full attosecond precision use [`Self::to_gps_wk_and_tow`].
140    #[inline]
141    pub const fn to_gps_tow_f(&self, current: Scale) -> Real {
142        let (_, tow) = self.to_gps_wk_and_tow(current);
143        tow.to_sec_f()
144    }
145
146    /// Creates a [`Dt`] in GPS Time from a GPS week number and
147    /// floating-point Time of Week.
148    ///
149    /// This is the floating-point counterpart to [`Self::from_gps_wk_and_tow`].
150    #[inline]
151    pub const fn from_gps_wk_and_tow_f(week: i64, tow: Real) -> Self {
152        let tow_span = Dt::from_sec_f(tow);
153        Self::from_gps_wk_and_tow(week, tow_span)
154    }
155
156    /// Returns the elapsed time since the GPS epoch as a [`Dt`] on the GPS scale.
157    ///
158    /// The GPS epoch is [`Dt::GPS_EPOCH`].
159    #[inline]
160    pub const fn to_gps(&self, current: Scale) -> Dt {
161        self.to(current, Scale::GPS)
162            .to_diff_raw(Dt::GPS_EPOCH.to(Scale::TAI, Scale::GPS))
163    }
164
165    /// Inverse of [`Self::to_gps`].
166    #[inline]
167    pub const fn from_gps(elapsed: Dt) -> Self {
168        Self::GPS_EPOCH.add(elapsed)
169    }
170
171    /// Floating-point version of [`Self::from_gps`].
172    #[inline]
173    pub const fn from_gps_f(elapsed_sec: Real) -> Self {
174        Self::from_gps(Dt::from_sec_f(elapsed_sec))
175    }
176
177    /// Returns only the GPS week number.
178    ///
179    /// Convenience method. For both the week number and the Time of Week, use
180    /// [`Self::to_gps_wk_and_tow`].
181    #[inline]
182    pub const fn to_gps_wk(&self, current: Scale) -> i64 {
183        self.to_gps_wk_and_tow(current).0
184    }
185
186    /// Returns the day of the GPS week (0 = Sunday, 1 = Monday, …, 6 = Saturday).
187    ///
188    /// This value is computed directly from the GPS Time of Week and is
189    /// independent of the Gregorian calendar or civil time.
190    pub const fn to_gps_day_of_wk(&self, current: Scale) -> u8 {
191        let (_, tow) = self.to_gps_wk_and_tow(current);
192        let secs = tow.to_attos() / ATTOS_PER_SEC_I128;
193
194        (secs / SEC_PER_DAYI64 as i128) as u8
195    }
196
197    /// Returns the elapsed time since the Chandra X-ray Center (CXC) epoch
198    /// as a [`Dt`] on the TT scale.
199    ///
200    /// The CXC epoch is [`Dt::CXC_EPOCH`].
201    #[inline]
202    pub const fn to_cxcsec(&self, current: Scale) -> Dt {
203        self.to(current, Scale::TT)
204            .to_diff_raw(Dt::CXC_EPOCH.to(Scale::TAI, Scale::TT))
205    }
206
207    /// Inverse of [`Self::to_cxcsec`].
208    #[inline]
209    pub const fn from_cxcsec(elapsed: Dt) -> Self {
210        Self::CXC_EPOCH.add(elapsed)
211    }
212
213    /// Floating-point counterpart of [`Self::from_cxcsec`].
214    #[inline]
215    pub const fn from_cxcsec_f(elapsed_sec: Real) -> Self {
216        Self::from_cxcsec(Dt::from_sec_f(elapsed_sec))
217    }
218
219    /// Returns the elapsed time since the GPS/Galileo Experiment (GALEX) epoch
220    /// as a [`Dt`] on the TAI scale.
221    ///
222    /// The GALEX epoch is [`Self::GPS_EPOCH`].
223    #[inline]
224    pub const fn to_galexsec(&self, current: Scale) -> Dt {
225        self.to(current, Scale::UTC)
226            .to_diff_raw(Dt::GPS_EPOCH.to(Scale::TAI, Scale::UTC))
227    }
228
229    /// Inverse of [`Self::to_galexsec`].
230    #[inline]
231    pub const fn from_galexsec(elapsed: Dt) -> Self {
232        Self::GPS_EPOCH
233            .to(Scale::TAI, Scale::UTC)
234            .add(elapsed)
235            .to(Scale::UTC, Scale::TAI)
236    }
237
238    /// Floating-point counterpart of [`Self::from_galexsec`].
239    #[inline]
240    pub const fn from_galexsec_f(elapsed_sec: Real) -> Self {
241        Self::from_galexsec(Dt::from_sec_f(elapsed_sec))
242    }
243}