use super::constats::GPS_EPOCH_TAI;
use super::context::TimeContext;
use super::error::ConversionError;
use super::scale::{TAI, UTC};
use super::time::Time;
use crate::data::active::{
time_data_tai_seconds_from_utc, time_data_tai_seconds_is_in_leap_window,
time_data_try_tai_minus_utc_mjd, time_data_utc_from_tai_seconds,
};
use crate::encoding::{mjd_to_j2000_seconds, unix_seconds_to_mjd};
use chrono::{DateTime, Utc};
use qtty::Second;
impl Time<UTC> {
#[inline]
pub fn try_from_chrono_with(
dt: DateTime<Utc>,
ctx: &TimeContext,
) -> Result<Self, ConversionError> {
let tai_secs =
time_data_tai_seconds_from_utc(ctx.time_data(), dt, ctx.allows_pre_definition_utc())?;
Self::try_new(tai_secs, Second::new(0.0))
}
#[inline]
pub fn try_from_chrono(dt: DateTime<Utc>) -> Result<Self, ConversionError> {
Self::try_from_chrono_with(dt, &TimeContext::new())
}
#[track_caller]
#[inline]
pub fn from_chrono_with(dt: DateTime<Utc>, ctx: &TimeContext) -> Self {
Self::try_from_chrono_with(dt, ctx)
.expect("UTC conversion failed; use try_from_chrono_with")
}
#[track_caller]
#[inline]
pub fn from_chrono(dt: DateTime<Utc>) -> Self {
Self::try_from_chrono(dt).expect("UTC conversion failed; use try_from_chrono")
}
#[inline]
pub fn try_to_chrono_with(self, ctx: &TimeContext) -> Result<DateTime<Utc>, ConversionError> {
time_data_utc_from_tai_seconds(
ctx.time_data(),
self.total_seconds(),
ctx.allows_pre_definition_utc(),
)
}
#[inline]
pub fn try_to_chrono(self) -> Result<DateTime<Utc>, ConversionError> {
self.try_to_chrono_with(&TimeContext::new())
}
#[inline]
pub fn to_chrono_with(self, ctx: &TimeContext) -> Option<DateTime<Utc>> {
self.try_to_chrono_with(ctx).ok()
}
#[inline]
pub fn to_chrono(self) -> Option<DateTime<Utc>> {
self.try_to_chrono().ok()
}
#[inline]
pub(crate) fn from_raw_unix_seconds_with(
seconds: Second,
ctx: &TimeContext,
) -> Result<Self, ConversionError> {
if !seconds.is_finite() {
return Err(ConversionError::NonFinite);
}
let mjd_utc = unix_seconds_to_mjd(seconds);
let tai_minus_utc = time_data_try_tai_minus_utc_mjd(
ctx.time_data(),
mjd_utc,
ctx.allows_pre_definition_utc(),
)?;
let tai_secs = mjd_to_j2000_seconds(mjd_utc) + tai_minus_utc;
Self::try_new(tai_secs, Second::new(0.0))
}
#[inline]
pub(crate) fn raw_unix_seconds_with(
self,
ctx: &TimeContext,
) -> Result<Second, ConversionError> {
if self.is_leap_second_with(ctx) {
return Err(ConversionError::InvalidLeapSecond);
}
let dt = self.try_to_chrono_with(ctx)?;
let nanos = dt.timestamp_subsec_nanos();
Ok(Second::new(dt.timestamp() as f64 + nanos as f64 / 1e9))
}
#[inline]
pub fn is_leap_second_with(self, ctx: &TimeContext) -> bool {
time_data_tai_seconds_is_in_leap_window(ctx.time_data(), self.total_seconds())
}
#[inline]
pub fn is_leap_second(self) -> bool {
self.is_leap_second_with(&TimeContext::new())
}
}
impl Time<TAI> {
#[inline]
pub(crate) fn from_raw_gps_seconds(seconds: Second) -> Result<Self, ConversionError> {
if !seconds.is_finite() {
return Err(ConversionError::NonFinite);
}
Self::try_new(seconds + GPS_EPOCH_TAI, Second::new(0.0))
}
#[inline]
pub(crate) fn raw_gps_seconds(self) -> Second {
self.total_seconds() - GPS_EPOCH_TAI
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::active::{active_time_data, with_test_time_data};
#[test]
fn chrono_convenience_wrappers_roundtrip_with_context() {
let bundle = active_time_data().as_ref().clone();
with_test_time_data(bundle, || {
let ctx = TimeContext::new();
let dt = DateTime::from_timestamp(946_728_000, 125_000_000).unwrap();
let with_ctx = Time::<UTC>::from_chrono_with(dt, &ctx);
let default_ctx = Time::<UTC>::from_chrono(dt);
assert_eq!(with_ctx, default_ctx);
let back_with_ctx = with_ctx.to_chrono_with(&ctx).unwrap();
let back_default = with_ctx.to_chrono().unwrap();
let with_ctx_delta_ns =
back_with_ctx.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
let default_delta_ns =
back_default.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
assert!(with_ctx_delta_ns.abs() < 50_000);
assert!(default_delta_ns.abs() < 50_000);
});
}
#[test]
fn gps_raw_seconds_reject_nonfinite_and_roundtrip() {
assert!(matches!(
Time::<TAI>::from_raw_gps_seconds(Second::new(f64::INFINITY)),
Err(ConversionError::NonFinite)
));
let tai = Time::<TAI>::from_raw_gps_seconds(Second::new(123.5)).unwrap();
assert_eq!(tai.raw_gps_seconds(), Second::new(123.5));
}
}