use chrono::TimeZone;
use crate::Instant;
#[inline]
fn datetime_to_unixtime<Tz>(dt: &chrono::DateTime<Tz>) -> f64
where
Tz: chrono::TimeZone,
{
dt.timestamp() as f64 + dt.timestamp_subsec_nanos() as f64 * 1.0e-9
}
impl From<Instant> for chrono::DateTime<chrono::Utc> {
fn from(inst: Instant) -> Self {
let unixtime = inst.as_unixtime();
let secs = unixtime.trunc() as i64;
let nsecs = ((unixtime.fract()) * 1.0e9) as u32;
chrono::Utc.timestamp_opt(secs, nsecs).unwrap()
}
}
impl From<&Instant> for chrono::DateTime<chrono::Utc> {
fn from(inst: &Instant) -> Self {
let unixtime = inst.as_unixtime();
let secs = unixtime.trunc() as i64;
let nsecs = ((unixtime.fract()) * 1.0e9) as u32;
chrono::Utc.timestamp_opt(secs, nsecs).unwrap()
}
}
impl<TZ> From<chrono::DateTime<TZ>> for Instant
where
TZ: chrono::TimeZone,
{
fn from(dt: chrono::DateTime<TZ>) -> Self {
Instant::from_unixtime(datetime_to_unixtime(&dt))
}
}
impl<TZ> From<&chrono::DateTime<TZ>> for Instant
where
TZ: chrono::TimeZone,
{
fn from(dt: &chrono::DateTime<TZ>) -> Self {
Instant::from_unixtime(datetime_to_unixtime(&dt))
}
}
mod chrono_impls {
use crate::{Instant, TimeLike, TimeScale};
use super::datetime_to_unixtime;
impl<Tz> TimeLike for chrono::DateTime<Tz>
where
Tz: chrono::TimeZone,
{
#[inline]
fn as_mjd_with_scale(&self, scale: TimeScale) -> f64 {
let unixtime = datetime_to_unixtime(&self);
Instant::from_unixtime(unixtime).as_mjd_with_scale(scale)
}
#[inline]
fn as_jd_with_scale(&self, scale: TimeScale) -> f64 {
let unixtime = datetime_to_unixtime(&self);
Instant::from_unixtime(unixtime).as_jd_with_scale(scale)
}
#[inline]
fn as_instant(&self) -> Instant {
Instant::from_unixtime(datetime_to_unixtime(&self))
}
}
}
#[cfg(test)]
mod tests {
use crate::{TimeLike, TimeScale};
use super::*;
#[test]
fn test_instant_chrono_conversion() {
let inst = Instant::from_datetime(2024, 1, 1, 12, 0, 0.0).unwrap();
let dt: chrono::DateTime<chrono::Utc> = chrono::DateTime::from(inst);
let inst_converted = Instant::from(dt);
assert!((inst.as_unixtime() - inst_converted.as_unixtime()).abs() < 1.0e-9);
let inst2 = dt.as_instant();
assert!((inst.as_unixtime() - inst2.as_unixtime()).abs() < 1.0e-9);
}
#[test]
fn test_timelike_trait_mjd_conversion() {
let inst = Instant::from_datetime(2024, 6, 15, 18, 30, 45.5).unwrap();
let dt: chrono::DateTime<chrono::Utc> = chrono::DateTime::from(inst);
let mjd_instant = inst.as_mjd_with_scale(TimeScale::UTC);
let mjd_chrono = dt.as_mjd_with_scale(TimeScale::UTC);
assert!((mjd_instant - mjd_chrono).abs() < 1.0e-9,
"MJD UTC mismatch: {} vs {}", mjd_instant, mjd_chrono);
let mjd_instant_tai = inst.as_mjd_with_scale(TimeScale::TAI);
let mjd_chrono_tai = dt.as_mjd_with_scale(TimeScale::TAI);
assert!((mjd_instant_tai - mjd_chrono_tai).abs() < 1.0e-9,
"MJD TAI mismatch: {} vs {}", mjd_instant_tai, mjd_chrono_tai);
let mjd_instant_tt = inst.as_mjd_with_scale(TimeScale::TT);
let mjd_chrono_tt = dt.as_mjd_with_scale(TimeScale::TT);
assert!((mjd_instant_tt - mjd_chrono_tt).abs() < 1.0e-9,
"MJD TT mismatch: {} vs {}", mjd_instant_tt, mjd_chrono_tt);
}
#[test]
fn test_timelike_trait_jd_conversion() {
let inst = Instant::from_datetime(2000, 1, 1, 12, 0, 0.0).unwrap();
let dt: chrono::DateTime<chrono::Utc> = chrono::DateTime::from(inst);
let jd_instant = inst.as_jd_with_scale(TimeScale::UTC);
let jd_chrono = dt.as_jd_with_scale(TimeScale::UTC);
assert!((jd_instant - jd_chrono).abs() < 1.0e-9,
"JD UTC mismatch: {} vs {}", jd_instant, jd_chrono);
let mjd_instant = inst.as_mjd_with_scale(TimeScale::UTC);
assert!((jd_instant - (mjd_instant + 2400000.5)).abs() < 1.0e-12,
"JD-MJD relationship incorrect");
}
#[test]
fn test_timelike_trait_as_instant() {
let inst1 = Instant::from_datetime(2024, 12, 25, 6, 30, 15.123).unwrap();
let dt: chrono::DateTime<chrono::Utc> = chrono::DateTime::from(inst1);
let inst2 = dt.as_instant();
assert!((inst1.as_unixtime() - inst2.as_unixtime()).abs() < 1.0e-9,
"as_instant() conversion failed");
}
#[test]
fn test_timelike_trait_with_timezone() {
let dt_utc = chrono::Utc.with_ymd_and_hms(2024, 3, 15, 12, 0, 0).unwrap();
let dt_fixed = chrono::DateTime::<chrono::FixedOffset>::from(dt_utc);
let mjd_utc = dt_utc.as_mjd_with_scale(TimeScale::UTC);
let mjd_fixed = dt_fixed.as_mjd_with_scale(TimeScale::UTC);
assert!((mjd_utc - mjd_fixed).abs() < 1.0e-9,
"MJD mismatch between timezones: {} vs {}", mjd_utc, mjd_fixed);
}
}