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