1use std::fmt::{Debug, Formatter};
5
6use crate::*;
7
8use chrono::Utc;
9
10pub trait Source {}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Triggerbox; impl Source for Triggerbox {}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct HostClock;
21impl Source for HostClock {}
22
23#[derive(Clone, PartialEq, Eq)]
26pub struct FlydraFloatTimestampLocal<S> {
27 value_f64: NotNan<f64>,
28 source: std::marker::PhantomData<S>,
29}
30
31impl<S> Debug for FlydraFloatTimestampLocal<S> {
32 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
33 let dt: chrono::DateTime<Utc> = self.into();
34 write!(f, "FlydraFloatTimestampLocal {{ {dt:?} }}")
35 }
36}
37
38impl<S, TZ: chrono::TimeZone> From<&chrono::DateTime<TZ>> for FlydraFloatTimestampLocal<S> {
39 fn from(orig: &chrono::DateTime<TZ>) -> Self {
40 FlydraFloatTimestampLocal::from_dt(orig)
41 }
42}
43
44impl<S, TZ: chrono::TimeZone> From<chrono::DateTime<TZ>> for FlydraFloatTimestampLocal<S> {
45 fn from(val: chrono::DateTime<TZ>) -> FlydraFloatTimestampLocal<S> {
46 FlydraFloatTimestampLocal::from_dt(&val)
47 }
48}
49
50impl TryFrom<PtpStamp> for FlydraFloatTimestampLocal<Triggerbox> {
51 type Error = &'static str;
52 fn try_from(
53 val: PtpStamp,
54 ) -> std::result::Result<FlydraFloatTimestampLocal<Triggerbox>, &'static str> {
55 let dt: chrono::DateTime<chrono::Utc> = val.try_into()?;
56 Ok(FlydraFloatTimestampLocal::from_dt(&dt))
57 }
58}
59
60impl<'a, S> From<&'a FlydraFloatTimestampLocal<S>> for chrono::DateTime<Utc> {
61 fn from(orig: &'a FlydraFloatTimestampLocal<S>) -> chrono::DateTime<Utc> {
62 strand_datetime_conversion::f64_to_datetime(orig.value_f64.into_inner())
63 }
64}
65
66impl<S> From<FlydraFloatTimestampLocal<S>> for chrono::DateTime<Utc> {
67 fn from(orig: FlydraFloatTimestampLocal<S>) -> chrono::DateTime<Utc> {
68 From::from(&orig)
69 }
70}
71
72assert_impl_all!(FlydraFloatTimestampLocal<Triggerbox>: PartialEq);
73
74impl<S> FlydraFloatTimestampLocal<S> {
75 pub fn from_dt<TZ: chrono::TimeZone>(dt: &chrono::DateTime<TZ>) -> Self {
77 let value_f64 = strand_datetime_conversion::datetime_to_f64(dt);
78 let value_f64 = value_f64.try_into().unwrap();
79 let source = std::marker::PhantomData;
80 Self { value_f64, source }
81 }
82
83 pub fn from_f64(value_f64: f64) -> Self {
85 assert!(
86 !value_f64.is_nan(),
87 "cannot convert NaN to FlydraFloatTimestampLocal"
88 );
89 Self::from_notnan_f64(value_f64.try_into().unwrap())
90 }
91
92 pub fn from_notnan_f64(value_f64: NotNan<f64>) -> Self {
94 let source = std::marker::PhantomData;
95 Self { value_f64, source }
96 }
97
98 #[inline(always)]
99 pub fn as_f64(&self) -> f64 {
101 self.value_f64.into()
102 }
103}
104
105#[inline]
111pub fn triggerbox_time(
112 clock_model: Option<&ClockModel>,
113 frame_offset: Option<u64>,
114 frame: usize,
115) -> Option<FlydraFloatTimestampLocal<Triggerbox>> {
116 let frame: u64 = frame.try_into().unwrap();
117 if let Some(frame_offset) = frame_offset
118 && let Some(cm) = clock_model
119 {
120 let ts: f64 = ((frame - frame_offset) as f64) * cm.gain + cm.offset;
121 let ts = FlydraFloatTimestampLocal::<Triggerbox>::from_f64(ts);
122 return Some(ts);
123 }
124 None
125}
126
127#[test]
128#[should_panic]
129fn test_nan_handling() {
130 let _ts = FlydraFloatTimestampLocal::<Triggerbox>::from_f64(f64::NAN);
131}
132
133#[test]
136fn ensure_conversion() {
137 use chrono::{DateTime, Utc};
138 let t1 = DateTime::<Utc>::from_timestamp(60, 123_456_789).unwrap();
139 let t2 = FlydraFloatTimestampLocal::<HostClock>::from(t1);
140 let t3 = t2.value_f64.into_inner();
141 assert!((t3 - 60.123456789).abs() < 1e-10);
142 let t4: DateTime<Utc> = (&t2).into();
143 assert_eq!(t1, t4);
144}