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}