1#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
5#[serde(transparent)]
6pub struct TransactionTime(pub chrono::DateTime<chrono::Utc>);
7
8impl TransactionTime {
9 pub fn now() -> Self {
11 Self(chrono::Utc::now())
12 }
13}
14
15#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
18pub struct ValidTime {
19 pub start: Option<chrono::DateTime<chrono::Utc>>,
21 pub end: Option<chrono::DateTime<chrono::Utc>>,
23 pub valid_time_confidence: f32,
25}
26
27impl ValidTime {
28 pub fn is_unknown(&self) -> bool {
30 self.start.is_none() && self.end.is_none()
31 }
32
33 pub fn is_temporally_incoherent(&self, tx_time: &TransactionTime) -> bool {
36 if let (Some(s), Some(e)) = (self.start, self.end) {
37 if s > e {
38 return true;
39 }
40 }
41 if let Some(s) = self.start {
42 if s > tx_time.0 {
43 return true;
44 }
45 }
46 false
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use chrono::Utc;
54
55 #[test]
56 fn valid_time_unknown_when_both_none() {
57 let vt = ValidTime { start: None, end: None, valid_time_confidence: 0.0 };
58 assert!(vt.is_unknown());
59 }
60
61 #[test]
62 fn valid_time_not_unknown_when_start_set() {
63 let vt = ValidTime { start: Some(Utc::now()), end: None, valid_time_confidence: 0.8 };
64 assert!(!vt.is_unknown());
65 }
66
67 #[test]
68 fn incoherent_when_start_after_end() {
69 let now = Utc::now();
70 let tx = TransactionTime(now);
71 let vt = ValidTime {
72 start: Some(now + chrono::Duration::hours(1)),
73 end: Some(now),
74 valid_time_confidence: 1.0,
75 };
76 assert!(vt.is_temporally_incoherent(&tx));
77 }
78
79 #[test]
80 fn incoherent_when_valid_start_after_tx_time() {
81 let now = Utc::now();
82 let tx = TransactionTime(now);
83 let vt = ValidTime {
84 start: Some(now + chrono::Duration::hours(1)),
85 end: None,
86 valid_time_confidence: 1.0,
87 };
88 assert!(vt.is_temporally_incoherent(&tx));
89 }
90
91 #[test]
92 fn coherent_normal_interval() {
93 let now = Utc::now();
94 let tx = TransactionTime(now);
95 let vt = ValidTime {
96 start: Some(now - chrono::Duration::days(1)),
97 end: Some(now),
98 valid_time_confidence: 0.9,
99 };
100 assert!(!vt.is_temporally_incoherent(&tx));
101 }
102
103 #[test]
104 fn transaction_time_ordering() {
105 let t1 = TransactionTime(Utc::now());
106 let t2 = TransactionTime(Utc::now() + chrono::Duration::seconds(1));
107 assert!(t1 < t2);
108 }
109
110 #[test]
111 fn transaction_time_serializes_as_bare_iso8601_string() {
112 use chrono::TimeZone;
113 let dt = Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
115 let tt = TransactionTime(dt);
116 let json = serde_json::to_string(&tt).unwrap();
117 assert!(json.starts_with('"'), "expected a bare JSON string, got: {json}");
119 assert!(json.contains("2024-01-15"), "expected date in serialized form, got: {json}");
120 let back: TransactionTime = serde_json::from_str(&json).unwrap();
121 assert_eq!(tt, back);
122 }
123
124 #[test]
125 fn valid_time_round_trip_serde() {
126 let vt = ValidTime { start: Some(Utc::now()), end: None, valid_time_confidence: 0.7 };
127 let json = serde_json::to_string(&vt).unwrap();
128 let back: ValidTime = serde_json::from_str(&json).unwrap();
129 assert_eq!(vt.start, back.start);
130 assert_eq!(vt.end, back.end);
131 }
132}