1use super::constats::GPS_EPOCH_TAI;
8use super::context::TimeContext;
9use super::error::ConversionError;
10use super::scale::{TAI, UTC};
11use super::time::Time;
12use crate::data::active::{
13 time_data_tai_seconds_from_utc, time_data_tai_seconds_is_in_leap_window,
14 time_data_try_tai_minus_utc_mjd, time_data_utc_from_tai_seconds,
15};
16use crate::encoding::{mjd_to_j2000_seconds, unix_seconds_to_mjd};
17use chrono::{DateTime, Utc};
18use qtty::Second;
19
20impl Time<UTC> {
21 #[inline]
24 pub fn try_from_chrono_with(
25 dt: DateTime<Utc>,
26 ctx: &TimeContext,
27 ) -> Result<Self, ConversionError> {
28 let tai_secs =
29 time_data_tai_seconds_from_utc(ctx.time_data(), dt, ctx.allows_pre_definition_utc())?;
30 Self::try_new(tai_secs, Second::new(0.0))
31 }
32
33 #[inline]
40 pub fn try_from_chrono(dt: DateTime<Utc>) -> Result<Self, ConversionError> {
41 Self::try_from_chrono_with(dt, &TimeContext::new())
42 }
43
44 #[track_caller]
47 #[inline]
48 pub fn from_chrono_with(dt: DateTime<Utc>, ctx: &TimeContext) -> Self {
49 Self::try_from_chrono_with(dt, ctx)
50 .expect("UTC conversion failed; use try_from_chrono_with")
51 }
52
53 #[track_caller]
59 #[inline]
60 pub fn from_chrono(dt: DateTime<Utc>) -> Self {
61 Self::try_from_chrono(dt).expect("UTC conversion failed; use try_from_chrono")
62 }
63
64 #[inline]
67 pub fn try_to_chrono_with(self, ctx: &TimeContext) -> Result<DateTime<Utc>, ConversionError> {
68 time_data_utc_from_tai_seconds(
69 ctx.time_data(),
70 self.total_seconds(),
71 ctx.allows_pre_definition_utc(),
72 )
73 }
74
75 #[inline]
82 pub fn try_to_chrono(self) -> Result<DateTime<Utc>, ConversionError> {
83 self.try_to_chrono_with(&TimeContext::new())
84 }
85
86 #[inline]
89 pub fn to_chrono_with(self, ctx: &TimeContext) -> Option<DateTime<Utc>> {
90 self.try_to_chrono_with(ctx).ok()
91 }
92
93 #[inline]
99 pub fn to_chrono(self) -> Option<DateTime<Utc>> {
100 self.try_to_chrono().ok()
101 }
102
103 #[inline]
106 pub(crate) fn from_raw_unix_seconds_with(
107 seconds: Second,
108 ctx: &TimeContext,
109 ) -> Result<Self, ConversionError> {
110 if !seconds.is_finite() {
111 return Err(ConversionError::NonFinite);
112 }
113 let mjd_utc = unix_seconds_to_mjd(seconds);
114 let tai_minus_utc = time_data_try_tai_minus_utc_mjd(
115 ctx.time_data(),
116 mjd_utc,
117 ctx.allows_pre_definition_utc(),
118 )?;
119 let tai_secs = mjd_to_j2000_seconds(mjd_utc) + tai_minus_utc;
120 Self::try_new(tai_secs, Second::new(0.0))
121 }
122
123 #[inline]
126 pub(crate) fn raw_unix_seconds_with(
127 self,
128 ctx: &TimeContext,
129 ) -> Result<Second, ConversionError> {
130 if self.is_leap_second_with(ctx) {
131 return Err(ConversionError::InvalidLeapSecond);
132 }
133 let dt = self.try_to_chrono_with(ctx)?;
134 let nanos = dt.timestamp_subsec_nanos();
135 Ok(Second::new(dt.timestamp() as f64 + nanos as f64 / 1e9))
136 }
137
138 #[inline]
141 pub fn is_leap_second_with(self, ctx: &TimeContext) -> bool {
142 time_data_tai_seconds_is_in_leap_window(ctx.time_data(), self.total_seconds())
143 }
144
145 #[inline]
152 pub fn is_leap_second(self) -> bool {
153 self.is_leap_second_with(&TimeContext::new())
154 }
155}
156
157impl Time<TAI> {
158 #[inline]
160 pub(crate) fn from_raw_gps_seconds(seconds: Second) -> Result<Self, ConversionError> {
161 if !seconds.is_finite() {
162 return Err(ConversionError::NonFinite);
163 }
164 Self::try_new(seconds + GPS_EPOCH_TAI, Second::new(0.0))
165 }
166
167 #[inline]
169 pub(crate) fn raw_gps_seconds(self) -> Second {
170 self.total_seconds() - GPS_EPOCH_TAI
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::data::active::{active_time_data, with_test_time_data};
178
179 #[test]
180 fn chrono_convenience_wrappers_roundtrip_with_context() {
181 let bundle = active_time_data().as_ref().clone();
182 with_test_time_data(bundle, || {
183 let ctx = TimeContext::new();
184 let dt = DateTime::from_timestamp(946_728_000, 125_000_000).unwrap();
185
186 let with_ctx = Time::<UTC>::from_chrono_with(dt, &ctx);
187 let default_ctx = Time::<UTC>::from_chrono(dt);
188 assert_eq!(with_ctx, default_ctx);
189
190 let back_with_ctx = with_ctx.to_chrono_with(&ctx).unwrap();
191 let back_default = with_ctx.to_chrono().unwrap();
192 let with_ctx_delta_ns =
193 back_with_ctx.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
194 let default_delta_ns =
195 back_default.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
196
197 assert!(with_ctx_delta_ns.abs() < 50_000);
198 assert!(default_delta_ns.abs() < 50_000);
199 });
200 }
201
202 #[test]
203 fn gps_raw_seconds_reject_nonfinite_and_roundtrip() {
204 assert!(matches!(
205 Time::<TAI>::from_raw_gps_seconds(Second::new(f64::INFINITY)),
206 Err(ConversionError::NonFinite)
207 ));
208
209 let tai = Time::<TAI>::from_raw_gps_seconds(Second::new(123.5)).unwrap();
210 assert_eq!(tai.raw_gps_seconds(), Second::new(123.5));
211 }
212}