1use chrono::{DateTime, Duration, Local, NaiveDate, TimeZone, Utc};
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum TemporalValue {
12 Date(NaiveDate),
14
15 DateTime(DateTime<Utc>),
17
18 Duration(Duration),
20}
21
22impl TemporalValue {
23 pub fn parse_date(date_str: &str) -> Result<Self, String> {
25 NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
26 .map(TemporalValue::Date)
27 .map_err(|e| format!("Invalid date format: {} (expected YYYY-MM-DD)", e))
28 }
29
30 pub fn parse_datetime(datetime_str: &str) -> Result<Self, String> {
32 DateTime::parse_from_rfc3339(datetime_str)
33 .map(|dt| TemporalValue::DateTime(dt.with_timezone(&Utc)))
34 .map_err(|e| format!("Invalid datetime format: {}", e))
35 }
36
37 pub fn parse_duration(duration_str: &str) -> Result<Self, String> {
39 if let Some(weeks_part) = duration_str.strip_prefix('P') {
42 if let Some(weeks) = weeks_part.strip_suffix('W') {
43 let weeks: i64 = weeks
44 .parse()
45 .map_err(|_| format!("Invalid duration: {}", duration_str))?;
46 return Ok(TemporalValue::Duration(Duration::weeks(weeks)));
47 }
48 }
49
50 if duration_str.starts_with('P') && duration_str.ends_with('D') {
52 let days_str = &duration_str[1..duration_str.len() - 1];
53 let days: i64 = days_str
54 .parse()
55 .map_err(|_| format!("Invalid duration: {}", duration_str))?;
56 return Ok(TemporalValue::Duration(Duration::days(days)));
57 }
58
59 if let Some(time_part) = duration_str.strip_prefix("PT") {
61 let mut total_secs = 0i64;
62
63 let mut current = String::new();
65 for ch in time_part.chars() {
66 match ch {
67 'H' => {
68 if let Ok(hours) = current.parse::<i64>() {
69 total_secs += hours * 3600;
70 }
71 current.clear();
72 }
73 'M' => {
74 if let Ok(mins) = current.parse::<i64>() {
75 total_secs += mins * 60;
76 }
77 current.clear();
78 }
79 'S' => {
80 if let Ok(secs) = current.parse::<i64>() {
81 total_secs += secs;
82 }
83 current.clear();
84 }
85 '.' => {
86 current.clear();
88 }
89 _ => current.push(ch),
90 }
91 }
92
93 return Ok(TemporalValue::Duration(Duration::seconds(total_secs)));
94 }
95
96 Err(format!(
97 "Invalid duration format: {} (expected ISO8601 format)",
98 duration_str
99 ))
100 }
101
102 pub fn type_name(&self) -> &'static str {
104 match self {
105 TemporalValue::Date(_) => "date",
106 TemporalValue::DateTime(_) => "datetime",
107 TemporalValue::Duration(_) => "duration",
108 }
109 }
110
111 pub fn today() -> Self {
113 TemporalValue::Date(Local::now().naive_local().date())
114 }
115
116 pub fn now() -> Self {
118 TemporalValue::DateTime(Utc::now())
119 }
120
121 pub fn add_duration(&self, duration: &TemporalValue) -> Result<TemporalValue, String> {
123 let dur = match duration {
124 TemporalValue::Duration(d) => *d,
125 _ => return Err("Can only add Duration to temporal values".to_string()),
126 };
127
128 match self {
129 TemporalValue::Date(date) => {
130 let naive_dt = date
131 .and_hms_opt(0, 0, 0)
132 .ok_or("Invalid date time combination")?;
133 let dt = Utc.from_utc_datetime(&naive_dt);
134 let result_dt = dt + dur;
135 Ok(TemporalValue::DateTime(result_dt))
136 }
137 TemporalValue::DateTime(dt) => Ok(TemporalValue::DateTime(*dt + dur)),
138 TemporalValue::Duration(d) => Ok(TemporalValue::Duration(*d + dur)),
139 }
140 }
141
142 pub fn subtract_duration(&self, duration: &TemporalValue) -> Result<TemporalValue, String> {
144 let dur = match duration {
145 TemporalValue::Duration(d) => *d,
146 _ => return Err("Can only subtract Duration from temporal values".to_string()),
147 };
148
149 match self {
150 TemporalValue::Date(date) => {
151 let naive_dt = date
152 .and_hms_opt(0, 0, 0)
153 .ok_or("Invalid date time combination")?;
154 let dt = Utc.from_utc_datetime(&naive_dt);
155 let result_dt = dt - dur;
156 Ok(TemporalValue::DateTime(result_dt))
157 }
158 TemporalValue::DateTime(dt) => Ok(TemporalValue::DateTime(*dt - dur)),
159 TemporalValue::Duration(d) => Ok(TemporalValue::Duration(*d - dur)),
160 }
161 }
162
163 pub fn difference(&self, other: &TemporalValue) -> Result<TemporalValue, String> {
165 match (self, other) {
166 (TemporalValue::Date(d1), TemporalValue::Date(d2)) => {
167 let dt1 = d1
168 .and_hms_opt(0, 0, 0)
169 .ok_or("Invalid date time combination")?;
170 let dt2 = d2
171 .and_hms_opt(0, 0, 0)
172 .ok_or("Invalid date time combination")?;
173 let diff = dt1.signed_duration_since(dt2);
174 Ok(TemporalValue::Duration(diff))
175 }
176 (TemporalValue::DateTime(dt1), TemporalValue::DateTime(dt2)) => {
177 let diff = dt1.signed_duration_since(*dt2);
178 Ok(TemporalValue::Duration(diff))
179 }
180 _ => Err(format!(
181 "Cannot compute difference between {} and {}",
182 self.type_name(),
183 other.type_name()
184 )),
185 }
186 }
187
188 pub fn compare(&self, other: &TemporalValue) -> Result<std::cmp::Ordering, String> {
190 match (self, other) {
191 (TemporalValue::Date(d1), TemporalValue::Date(d2)) => Ok(d1.cmp(d2)),
192 (TemporalValue::DateTime(dt1), TemporalValue::DateTime(dt2)) => Ok(dt1.cmp(dt2)),
193 (TemporalValue::Duration(d1), TemporalValue::Duration(d2)) => Ok(d1.cmp(d2)),
194 _ => Err(format!(
195 "Cannot compare {} with {}",
196 self.type_name(),
197 other.type_name()
198 )),
199 }
200 }
201
202 pub fn is_before(&self, other: &TemporalValue) -> Result<bool, String> {
204 Ok(self.compare(other)? == std::cmp::Ordering::Less)
205 }
206
207 pub fn is_after(&self, other: &TemporalValue) -> Result<bool, String> {
209 Ok(self.compare(other)? == std::cmp::Ordering::Greater)
210 }
211
212 pub fn days(&self) -> Result<i64, String> {
214 match self {
215 TemporalValue::Duration(d) => Ok(d.num_days()),
216 _ => Err(format!("Cannot get days from {}", self.type_name())),
217 }
218 }
219
220 pub fn seconds(&self) -> Result<i64, String> {
222 match self {
223 TemporalValue::Duration(d) => Ok(d.num_seconds()),
224 _ => Err(format!("Cannot get seconds from {}", self.type_name())),
225 }
226 }
227
228 pub fn start_of_day(&self) -> Result<TemporalValue, String> {
230 match self {
231 TemporalValue::Date(date) => {
232 let dt = date
233 .and_hms_opt(0, 0, 0)
234 .ok_or("Invalid date time combination")?;
235 Ok(TemporalValue::DateTime(Utc.from_utc_datetime(&dt)))
236 }
237 TemporalValue::DateTime(dt) => {
238 let date = dt.date_naive();
239 let start_dt = date
240 .and_hms_opt(0, 0, 0)
241 .ok_or("Invalid date time combination")?;
242 Ok(TemporalValue::DateTime(Utc.from_utc_datetime(&start_dt)))
243 }
244 _ => Err(format!("Cannot get start of day from {}", self.type_name())),
245 }
246 }
247
248 pub fn end_of_day(&self) -> Result<TemporalValue, String> {
250 match self {
251 TemporalValue::Date(date) => {
252 let dt = date
253 .and_hms_opt(23, 59, 59)
254 .ok_or("Invalid date time combination")?;
255 Ok(TemporalValue::DateTime(Utc.from_utc_datetime(&dt)))
256 }
257 TemporalValue::DateTime(dt) => {
258 let date = dt.date_naive();
259 let end_dt = date
260 .and_hms_opt(23, 59, 59)
261 .ok_or("Invalid date time combination")?;
262 Ok(TemporalValue::DateTime(Utc.from_utc_datetime(&end_dt)))
263 }
264 _ => Err(format!("Cannot get end of day from {}", self.type_name())),
265 }
266 }
267
268 pub fn to_iso8601(&self) -> String {
270 match self {
271 TemporalValue::Date(date) => date.to_string(),
272 TemporalValue::DateTime(dt) => dt.to_rfc3339(),
273 TemporalValue::Duration(d) => {
274 let days = d.num_days();
275 let secs = d.num_seconds() % 86400;
276 format!("P{}DT{}S", days, secs)
277 }
278 }
279 }
280}
281
282impl fmt::Display for TemporalValue {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 write!(f, "{}", self.to_iso8601())
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use chrono::Datelike;
292
293 #[test]
294 fn test_parse_date() {
295 let date = TemporalValue::parse_date("2024-01-15").unwrap();
296 match date {
297 TemporalValue::Date(d) => {
298 assert_eq!(d.year(), 2024);
299 assert_eq!(d.month(), 1);
300 assert_eq!(d.day(), 15);
301 }
302 _ => panic!("Expected Date"),
303 }
304 }
305
306 #[test]
307 fn test_parse_duration_days() {
308 let duration = TemporalValue::parse_duration("P5D").unwrap();
309 match duration {
310 TemporalValue::Duration(d) => {
311 assert_eq!(d.num_days(), 5);
312 }
313 _ => panic!("Expected Duration"),
314 }
315 }
316
317 #[test]
318 fn test_parse_duration_hours() {
319 let duration = TemporalValue::parse_duration("PT2H").unwrap();
320 match duration {
321 TemporalValue::Duration(d) => {
322 assert_eq!(d.num_hours(), 2);
323 }
324 _ => panic!("Expected Duration"),
325 }
326 }
327
328 #[test]
329 fn test_add_duration_to_date() {
330 let date = TemporalValue::parse_date("2024-01-15").unwrap();
331 let duration = TemporalValue::parse_duration("P5D").unwrap();
332 let result = date.add_duration(&duration).unwrap();
333
334 match result {
335 TemporalValue::DateTime(dt) => {
336 assert_eq!(dt.date_naive().day(), 20);
337 }
338 _ => panic!("Expected DateTime"),
339 }
340 }
341
342 #[test]
343 fn test_subtract_duration_from_date() {
344 let date = TemporalValue::parse_date("2024-01-15").unwrap();
345 let duration = TemporalValue::parse_duration("P10D").unwrap();
346 let result = date.subtract_duration(&duration).unwrap();
347
348 match result {
349 TemporalValue::DateTime(dt) => {
350 assert_eq!(dt.date_naive().day(), 5);
351 }
352 _ => panic!("Expected DateTime"),
353 }
354 }
355
356 #[test]
357 fn test_date_comparison() {
358 let date1 = TemporalValue::parse_date("2024-01-15").unwrap();
359 let date2 = TemporalValue::parse_date("2024-01-20").unwrap();
360
361 assert!(date1.is_before(&date2).unwrap());
362 assert!(date2.is_after(&date1).unwrap());
363 }
364
365 #[test]
366 fn test_difference_between_dates() {
367 let date1 = TemporalValue::parse_date("2024-01-15").unwrap();
368 let date2 = TemporalValue::parse_date("2024-01-20").unwrap();
369
370 let diff = date2.difference(&date1).unwrap();
371 match diff {
372 TemporalValue::Duration(d) => {
373 assert_eq!(d.num_days(), 5);
374 }
375 _ => panic!("Expected Duration"),
376 }
377 }
378
379 #[test]
380 fn test_duration_arithmetic() {
381 let d1 = TemporalValue::parse_duration("P2D").unwrap();
382 let d2 = TemporalValue::parse_duration("P3D").unwrap();
383
384 let sum = d1.add_duration(&d2).unwrap();
385 match sum {
386 TemporalValue::Duration(d) => {
387 assert_eq!(d.num_days(), 5);
388 }
389 _ => panic!("Expected Duration"),
390 }
391 }
392
393 #[test]
394 fn test_start_end_of_day() {
395 let date = TemporalValue::parse_date("2024-01-15").unwrap();
396
397 let start = date.start_of_day().unwrap();
398 let end = date.end_of_day().unwrap();
399
400 match (&start, &end) {
401 (TemporalValue::DateTime(dt1), TemporalValue::DateTime(dt2)) => {
402 assert_eq!(dt1.date_naive().day(), dt2.date_naive().day());
403 assert_eq!(dt1.format("%H:%M:%S").to_string(), "00:00:00");
404 assert_eq!(dt2.format("%H:%M:%S").to_string(), "23:59:59");
405 }
406 _ => panic!("Expected DateTimes"),
407 }
408 }
409
410 #[test]
411 fn test_invalid_date_parsing() {
412 let result = TemporalValue::parse_date("2024-13-45");
413 assert!(result.is_err());
414 }
415
416 #[test]
417 fn test_today() {
418 let today = TemporalValue::today();
419 match today {
420 TemporalValue::Date(_) => {}
421 _ => panic!("Expected Date"),
422 }
423 }
424
425 #[test]
426 fn test_type_name() {
427 assert_eq!(
428 TemporalValue::parse_date("2024-01-15").unwrap().type_name(),
429 "date"
430 );
431 assert_eq!(
432 TemporalValue::parse_duration("P1D").unwrap().type_name(),
433 "duration"
434 );
435 }
436}