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