1use chrono::{DateTime, NaiveDate, Utc};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
9pub struct CognifyTimestamp {
10 pub year: u16,
11 pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: u8, pub time_at: i64,
18 pub timestamp_str: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
26pub struct CognifyInterval {
27 pub time_from: CognifyTimestamp,
28 pub time_to: CognifyTimestamp,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
34pub struct TemporalEvent {
35 pub name: String,
36 pub description: Option<String>,
37 pub location: Option<String>,
38 pub at: Option<CognifyTimestamp>,
41 pub during: Option<CognifyInterval>,
45 #[serde(default)]
47 pub attributes: Vec<EventAttribute>,
48}
49
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
52pub struct EventAttribute {
53 pub entity: String,
54 pub entity_type: String,
55 pub relationship: String,
57}
58
59fn default_month() -> u8 {
60 1
61}
62
63fn default_day() -> u8 {
64 1
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
70pub struct RawExtractedTimestamp {
71 pub year: u16,
72 #[serde(default = "default_month")]
73 pub month: u8, #[serde(default = "default_day")]
75 pub day: u8, #[serde(default)]
77 pub hour: u8, #[serde(default)]
79 pub minute: u8, #[serde(default)]
81 pub second: u8, }
83
84pub fn to_cognify_timestamp(raw: RawExtractedTimestamp) -> Option<CognifyTimestamp> {
87 let naive = NaiveDate::from_ymd_opt(raw.year as i32, raw.month as u32, raw.day as u32)?
88 .and_hms_opt(raw.hour as u32, raw.minute as u32, raw.second as u32)?;
89 let time_at = DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc).timestamp_millis(); let timestamp_str = format!(
91 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
92 raw.year, raw.month, raw.day, raw.hour, raw.minute, raw.second
93 );
94 Some(CognifyTimestamp {
95 year: raw.year,
96 month: raw.month,
97 day: raw.day,
98 hour: raw.hour,
99 minute: raw.minute,
100 second: raw.second,
101 time_at,
102 timestamp_str,
103 })
104}
105
106#[cfg(test)]
107#[allow(
108 clippy::unwrap_used,
109 clippy::expect_used,
110 reason = "test code — panics are acceptable failures"
111)]
112mod tests {
113 use super::*;
114 use chrono::{TimeZone, Utc};
115
116 #[test]
117 fn to_cognify_timestamp_happy_path() {
118 let raw = RawExtractedTimestamp {
119 year: 2024,
120 month: 3,
121 day: 15,
122 hour: 0,
123 minute: 0,
124 second: 0,
125 };
126 let result = to_cognify_timestamp(raw).unwrap();
127
128 let expected_dt = Utc.with_ymd_and_hms(2024, 3, 15, 0, 0, 0).unwrap();
129 assert_eq!(result.time_at, expected_dt.timestamp_millis());
130 assert_eq!(result.timestamp_str, "2024-03-15 00:00:00");
131 assert_eq!(result.year, 2024);
132 assert_eq!(result.month, 3);
133 assert_eq!(result.day, 15);
134 }
135
136 #[test]
137 fn to_cognify_timestamp_with_time_components() {
138 let raw = RawExtractedTimestamp {
139 year: 2024,
140 month: 7,
141 day: 4,
142 hour: 14,
143 minute: 30,
144 second: 45,
145 };
146 let result = to_cognify_timestamp(raw).unwrap();
147
148 let expected_dt = Utc.with_ymd_and_hms(2024, 7, 4, 14, 30, 45).unwrap();
149 assert_eq!(result.time_at, expected_dt.timestamp_millis());
150 assert_eq!(result.timestamp_str, "2024-07-04 14:30:45");
151 assert_eq!(result.hour, 14);
152 assert_eq!(result.minute, 30);
153 assert_eq!(result.second, 45);
154 }
155
156 #[test]
157 fn to_cognify_timestamp_invalid_dates_return_none() {
158 let raw = RawExtractedTimestamp {
160 year: 2024,
161 month: 13,
162 day: 1,
163 hour: 0,
164 minute: 0,
165 second: 0,
166 };
167 assert!(to_cognify_timestamp(raw).is_none());
168
169 let raw = RawExtractedTimestamp {
171 year: 2024,
172 month: 2,
173 day: 30,
174 hour: 0,
175 minute: 0,
176 second: 0,
177 };
178 assert!(to_cognify_timestamp(raw).is_none());
179 }
180
181 #[test]
182 fn to_cognify_timestamp_serde_defaults() {
183 let raw = RawExtractedTimestamp {
185 year: 1889,
186 month: 1,
187 day: 1,
188 hour: 0,
189 minute: 0,
190 second: 0,
191 };
192 let result = to_cognify_timestamp(raw).unwrap();
193
194 let expected_dt = Utc.with_ymd_and_hms(1889, 1, 1, 0, 0, 0).unwrap();
195 assert_eq!(result.time_at, expected_dt.timestamp_millis());
196 assert_eq!(result.timestamp_str, "1889-01-01 00:00:00");
197 assert_eq!(result.year, 1889);
198 assert_eq!(result.month, 1);
199 assert_eq!(result.day, 1);
200 }
201}