1use crate::evaluation::OperationResult;
7use crate::planning::semantics::{
8 ArithmeticComputation, ComparisonComputation, LiteralValue, SemanticDateTime,
9 SemanticDurationUnit, SemanticTime, SemanticTimezone, ValueKind,
10};
11use chrono::{
12 DateTime, Datelike, Duration as ChronoDuration, FixedOffset, NaiveDate, NaiveDateTime,
13 NaiveTime, TimeZone, Timelike,
14};
15use rust_decimal::prelude::ToPrimitive;
16use rust_decimal::Decimal;
17
18const SECONDS_PER_HOUR: i32 = 3600;
19const SECONDS_PER_MINUTE: i32 = 60;
20const MONTHS_PER_YEAR: u32 = 12;
21const MILLISECONDS_PER_SECOND: f64 = 1000.0;
22
23const EPOCH_YEAR: i32 = 1970;
24const EPOCH_MONTH: u32 = 1;
25const EPOCH_DAY: u32 = 1;
26
27fn create_semantic_timezone_offset(
28 timezone: &Option<SemanticTimezone>,
29) -> Result<FixedOffset, String> {
30 if let Some(tz) = timezone {
31 let offset_seconds = (tz.offset_hours as i32 * SECONDS_PER_HOUR)
32 + (tz.offset_minutes as i32 * SECONDS_PER_MINUTE);
33 FixedOffset::east_opt(offset_seconds).ok_or_else(|| {
34 format!(
35 "Invalid timezone offset: {}:{}",
36 tz.offset_hours, tz.offset_minutes
37 )
38 })
39 } else {
40 FixedOffset::east_opt(0).ok_or_else(|| "Failed to create UTC offset".to_string())
41 }
42}
43
44pub fn datetime_arithmetic(
46 left: &LiteralValue,
47 op: &ArithmeticComputation,
48 right: &LiteralValue,
49) -> OperationResult {
50 match (&left.value, &right.value, op) {
51 (ValueKind::Date(date), ValueKind::Duration(value, unit), ArithmeticComputation::Add) => {
52 let dt = match semantic_datetime_to_chrono(date) {
53 Ok(d) => d,
54 Err(msg) => return OperationResult::Veto(Some(msg)),
55 };
56
57 let new_dt = match unit {
58 SemanticDurationUnit::Month => {
59 let months = match value.to_i32() {
60 Some(m) => m,
61 None => {
62 return OperationResult::Veto(Some("Month value too large".to_string()))
63 }
64 };
65 match dt.checked_add_months(chrono::Months::new(months as u32)) {
66 Some(d) => d,
67 None => return OperationResult::Veto(Some("Date overflow".to_string())),
68 }
69 }
70 SemanticDurationUnit::Year => {
71 let years = match value.to_i32() {
72 Some(y) => y,
73 None => {
74 return OperationResult::Veto(Some("Year value too large".to_string()))
75 }
76 };
77 match dt.checked_add_months(chrono::Months::new(
78 (years * MONTHS_PER_YEAR as i32) as u32,
79 )) {
80 Some(d) => d,
81 None => return OperationResult::Veto(Some("Date overflow".to_string())),
82 }
83 }
84 _ => {
85 let seconds = super::units::duration_to_seconds(*value, unit);
86 let duration = match seconds_to_chrono_duration(seconds) {
87 Ok(d) => d,
88 Err(msg) => return OperationResult::Veto(Some(msg)),
89 };
90 match dt.checked_add_signed(duration) {
91 Some(d) => d,
92 None => return OperationResult::Veto(Some("Date overflow".to_string())),
93 }
94 }
95 };
96
97 OperationResult::Value(Box::new(LiteralValue::date_with_type(
98 chrono_to_semantic_datetime(new_dt),
99 left.lemma_type.clone(),
100 )))
101 }
102
103 (
104 ValueKind::Date(date),
105 ValueKind::Duration(value, unit),
106 ArithmeticComputation::Subtract,
107 ) => {
108 let dt = match semantic_datetime_to_chrono(date) {
109 Ok(d) => d,
110 Err(msg) => return OperationResult::Veto(Some(msg)),
111 };
112
113 let new_dt = match unit {
114 SemanticDurationUnit::Month => {
115 let months = match value.to_i32() {
116 Some(m) => m,
117 None => {
118 return OperationResult::Veto(Some("Month value too large".to_string()))
119 }
120 };
121 match dt.checked_sub_months(chrono::Months::new(months as u32)) {
122 Some(d) => d,
123 None => return OperationResult::Veto(Some("Date overflow".to_string())),
124 }
125 }
126 SemanticDurationUnit::Year => {
127 let years = match value.to_i32() {
128 Some(y) => y,
129 None => {
130 return OperationResult::Veto(Some("Year value too large".to_string()))
131 }
132 };
133 match dt.checked_sub_months(chrono::Months::new(
134 (years * MONTHS_PER_YEAR as i32) as u32,
135 )) {
136 Some(d) => d,
137 None => return OperationResult::Veto(Some("Date overflow".to_string())),
138 }
139 }
140 _ => {
141 let seconds = super::units::duration_to_seconds(*value, unit);
142 let duration = match seconds_to_chrono_duration(seconds) {
143 Ok(d) => d,
144 Err(msg) => return OperationResult::Veto(Some(msg)),
145 };
146 match dt.checked_sub_signed(duration) {
147 Some(d) => d,
148 None => return OperationResult::Veto(Some("Date overflow".to_string())),
149 }
150 }
151 };
152
153 OperationResult::Value(Box::new(LiteralValue::date_with_type(
154 chrono_to_semantic_datetime(new_dt),
155 left.lemma_type.clone(),
156 )))
157 }
158
159 (
160 ValueKind::Date(left_date),
161 ValueKind::Date(right_date),
162 ArithmeticComputation::Subtract,
163 ) => {
164 let left_dt = match semantic_datetime_to_chrono(left_date) {
165 Ok(d) => d,
166 Err(msg) => return OperationResult::Veto(Some(msg)),
167 };
168 let right_dt = match semantic_datetime_to_chrono(right_date) {
169 Ok(d) => d,
170 Err(msg) => return OperationResult::Veto(Some(msg)),
171 };
172 let duration = left_dt - right_dt;
173
174 let seconds = Decimal::from(duration.num_seconds());
175 OperationResult::Value(Box::new(LiteralValue::duration(
176 seconds,
177 SemanticDurationUnit::Second,
178 )))
179 }
180
181 (ValueKind::Duration(value, unit), ValueKind::Date(date), ArithmeticComputation::Add) => {
183 let dt = match semantic_datetime_to_chrono(date) {
184 Ok(d) => d,
185 Err(msg) => return OperationResult::Veto(Some(msg)),
186 };
187
188 let new_dt = match unit {
189 SemanticDurationUnit::Month => {
190 let months = match value.to_i32() {
191 Some(m) => m,
192 None => {
193 return OperationResult::Veto(Some("Month value too large".to_string()))
194 }
195 };
196 match dt.checked_add_months(chrono::Months::new(months as u32)) {
197 Some(d) => d,
198 None => return OperationResult::Veto(Some("Date overflow".to_string())),
199 }
200 }
201 SemanticDurationUnit::Year => {
202 let years = match value.to_i32() {
203 Some(y) => y,
204 None => {
205 return OperationResult::Veto(Some("Year value too large".to_string()))
206 }
207 };
208 match dt.checked_add_months(chrono::Months::new(
209 (years * MONTHS_PER_YEAR as i32) as u32,
210 )) {
211 Some(d) => d,
212 None => return OperationResult::Veto(Some("Date overflow".to_string())),
213 }
214 }
215 _ => {
216 let seconds = super::units::duration_to_seconds(*value, unit);
217 let duration = match seconds_to_chrono_duration(seconds) {
218 Ok(d) => d,
219 Err(msg) => return OperationResult::Veto(Some(msg)),
220 };
221 match dt.checked_add_signed(duration) {
222 Some(d) => d,
223 None => return OperationResult::Veto(Some("Date overflow".to_string())),
224 }
225 }
226 };
227
228 OperationResult::Value(Box::new(LiteralValue::date_with_type(
229 chrono_to_semantic_datetime(new_dt),
230 right.lemma_type.clone(),
231 )))
232 }
233
234 (ValueKind::Date(date), ValueKind::Time(time), ArithmeticComputation::Subtract) => {
235 let date_dt = match semantic_datetime_to_chrono(date) {
238 Ok(d) => d,
239 Err(msg) => return OperationResult::Veto(Some(msg)),
240 };
241
242 let naive_date = match NaiveDate::from_ymd_opt(date.year, date.month, date.day) {
244 Some(d) => d,
245 None => {
246 return OperationResult::Veto(Some(format!(
247 "Invalid date: {}-{}-{}",
248 date.year, date.month, date.day
249 )))
250 }
251 };
252 let naive_time = match NaiveTime::from_hms_opt(time.hour, time.minute, time.second) {
253 Some(t) => t,
254 None => {
255 return OperationResult::Veto(Some(format!(
256 "Invalid time: {}:{}:{}",
257 time.hour, time.minute, time.second
258 )))
259 }
260 };
261 let naive_dt = NaiveDateTime::new(naive_date, naive_time);
262
263 let offset = match create_semantic_timezone_offset(&date.timezone) {
265 Ok(o) => o,
266 Err(msg) => return OperationResult::Veto(Some(msg)),
267 };
268 let time_dt = match offset.from_local_datetime(&naive_dt).single() {
269 Some(dt) => dt,
270 None => {
271 return OperationResult::Veto(Some(
272 "Ambiguous or invalid datetime for timezone".to_string(),
273 ))
274 }
275 };
276
277 let duration = date_dt - time_dt;
278 let seconds = Decimal::from(duration.num_seconds());
279 OperationResult::Value(Box::new(LiteralValue::duration(
280 seconds,
281 SemanticDurationUnit::Second,
282 )))
283 }
284
285 _ => OperationResult::Veto(Some(format!(
286 "DateTime arithmetic operation {:?} not supported for these operand types",
287 op
288 ))),
289 }
290}
291
292fn semantic_datetime_to_chrono(date: &SemanticDateTime) -> Result<DateTime<FixedOffset>, String> {
293 let naive_date = NaiveDate::from_ymd_opt(date.year, date.month, date.day)
294 .ok_or_else(|| format!("Invalid date: {}-{}-{}", date.year, date.month, date.day))?;
295
296 let naive_time =
297 NaiveTime::from_hms_opt(date.hour, date.minute, date.second).ok_or_else(|| {
298 format!(
299 "Invalid time: {}:{}:{}",
300 date.hour, date.minute, date.second
301 )
302 })?;
303
304 let naive_dt = NaiveDateTime::new(naive_date, naive_time);
305
306 let offset = create_semantic_timezone_offset(&date.timezone)?;
307 offset
308 .from_local_datetime(&naive_dt)
309 .single()
310 .ok_or_else(|| "Ambiguous or invalid datetime for timezone".to_string())
311}
312
313fn chrono_to_semantic_datetime(dt: DateTime<FixedOffset>) -> SemanticDateTime {
314 let offset_seconds = dt.offset().local_minus_utc();
315 let offset_hours = (offset_seconds / SECONDS_PER_HOUR) as i8;
316 let offset_minutes = ((offset_seconds.abs() % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
317
318 SemanticDateTime {
319 year: dt.year(),
320 month: dt.month(),
321 day: dt.day(),
322 hour: dt.hour(),
323 minute: dt.minute(),
324 second: dt.second(),
325 timezone: Some(SemanticTimezone {
326 offset_hours,
327 offset_minutes,
328 }),
329 }
330}
331
332fn seconds_to_chrono_duration(seconds: Decimal) -> Result<ChronoDuration, String> {
333 let seconds_f64 = seconds
334 .to_f64()
335 .ok_or_else(|| "Duration conversion failed".to_string())?;
336
337 let milliseconds = (seconds_f64 * MILLISECONDS_PER_SECOND) as i64;
338 Ok(ChronoDuration::milliseconds(milliseconds))
339}
340
341pub fn datetime_comparison(
343 left: &LiteralValue,
344 op: &ComparisonComputation,
345 right: &LiteralValue,
346) -> OperationResult {
347 match (&left.value, &right.value) {
348 (ValueKind::Date(l), ValueKind::Date(r)) => {
349 let l_dt = match semantic_datetime_to_chrono(l) {
350 Ok(d) => d,
351 Err(msg) => return OperationResult::Veto(Some(msg)),
352 };
353 let r_dt = match semantic_datetime_to_chrono(r) {
354 Ok(d) => d,
355 Err(msg) => return OperationResult::Veto(Some(msg)),
356 };
357
358 let l_utc = l_dt.naive_utc();
359 let r_utc = r_dt.naive_utc();
360
361 let result = match op {
362 ComparisonComputation::GreaterThan => l_utc > r_utc,
363 ComparisonComputation::LessThan => l_utc < r_utc,
364 ComparisonComputation::GreaterThanOrEqual => l_utc >= r_utc,
365 ComparisonComputation::LessThanOrEqual => l_utc <= r_utc,
366 ComparisonComputation::Equal | ComparisonComputation::Is => l_utc == r_utc,
367 ComparisonComputation::NotEqual | ComparisonComputation::IsNot => l_utc != r_utc,
368 };
369
370 OperationResult::Value(Box::new(LiteralValue::from_bool(result)))
371 }
372
373 _ => OperationResult::Veto(Some("Invalid datetime comparison operands".to_string())),
374 }
375}
376
377pub fn time_comparison(
379 left: &LiteralValue,
380 op: &ComparisonComputation,
381 right: &LiteralValue,
382) -> OperationResult {
383 match (&left.value, &right.value) {
384 (ValueKind::Time(l), ValueKind::Time(r)) => {
385 let l_dt = match semantic_time_to_chrono_datetime(l) {
386 Ok(d) => d,
387 Err(msg) => return OperationResult::Veto(Some(msg)),
388 };
389 let r_dt = match semantic_time_to_chrono_datetime(r) {
390 Ok(d) => d,
391 Err(msg) => return OperationResult::Veto(Some(msg)),
392 };
393
394 let l_utc = l_dt.naive_utc();
395 let r_utc = r_dt.naive_utc();
396
397 let result = match op {
398 ComparisonComputation::GreaterThan => l_utc > r_utc,
399 ComparisonComputation::LessThan => l_utc < r_utc,
400 ComparisonComputation::GreaterThanOrEqual => l_utc >= r_utc,
401 ComparisonComputation::LessThanOrEqual => l_utc <= r_utc,
402 ComparisonComputation::Equal | ComparisonComputation::Is => l_utc == r_utc,
403 ComparisonComputation::NotEqual | ComparisonComputation::IsNot => l_utc != r_utc,
404 };
405
406 OperationResult::Value(Box::new(LiteralValue::from_bool(result)))
407 }
408 _ => unreachable!(
409 "BUG: time_comparison called with non-time operands; this should be enforced by planning and dispatch"
410 ),
411 }
412}
413
414pub fn time_arithmetic(
416 left: &LiteralValue,
417 op: &ArithmeticComputation,
418 right: &LiteralValue,
419) -> OperationResult {
420 match (&left.value, &right.value, op) {
421 (ValueKind::Time(time), ValueKind::Duration(value, unit), ArithmeticComputation::Add) => {
422 let seconds = super::units::duration_to_seconds(*value, unit);
423 let time_aware = match semantic_time_to_chrono_datetime(time) {
424 Ok(d) => d,
425 Err(msg) => return OperationResult::Veto(Some(msg)),
426 };
427 let duration = match seconds_to_chrono_duration(seconds) {
428 Ok(d) => d,
429 Err(msg) => return OperationResult::Veto(Some(msg)),
430 };
431 let result_dt = time_aware + duration;
432 OperationResult::Value(Box::new(LiteralValue::time_with_type(
433 chrono_datetime_to_semantic_time(result_dt),
434 left.lemma_type.clone(),
435 )))
436 }
437
438 (
439 ValueKind::Time(time),
440 ValueKind::Duration(value, unit),
441 ArithmeticComputation::Subtract,
442 ) => {
443 let seconds = super::units::duration_to_seconds(*value, unit);
444 let time_aware = match semantic_time_to_chrono_datetime(time) {
445 Ok(d) => d,
446 Err(msg) => return OperationResult::Veto(Some(msg)),
447 };
448 let duration = match seconds_to_chrono_duration(seconds) {
449 Ok(d) => d,
450 Err(msg) => return OperationResult::Veto(Some(msg)),
451 };
452 let result_dt = time_aware - duration;
453 OperationResult::Value(Box::new(LiteralValue::time_with_type(
454 chrono_datetime_to_semantic_time(result_dt),
455 left.lemma_type.clone(),
456 )))
457 }
458
459 (
460 ValueKind::Time(left_time),
461 ValueKind::Time(right_time),
462 ArithmeticComputation::Subtract,
463 ) => {
464 let left_dt = match semantic_time_to_chrono_datetime(left_time) {
465 Ok(d) => d,
466 Err(msg) => return OperationResult::Veto(Some(msg)),
467 };
468 let right_dt = match semantic_time_to_chrono_datetime(right_time) {
469 Ok(d) => d,
470 Err(msg) => return OperationResult::Veto(Some(msg)),
471 };
472
473 let diff = left_dt.naive_utc() - right_dt.naive_utc();
474 let diff_seconds = diff.num_seconds();
475 let seconds = Decimal::from(diff_seconds);
476
477 OperationResult::Value(Box::new(LiteralValue::duration(
478 seconds,
479 SemanticDurationUnit::Second,
480 )))
481 }
482
483 (ValueKind::Duration(value, unit), ValueKind::Time(time), ArithmeticComputation::Add) => {
485 let seconds = super::units::duration_to_seconds(*value, unit);
486 let time_aware = match semantic_time_to_chrono_datetime(time) {
487 Ok(d) => d,
488 Err(msg) => return OperationResult::Veto(Some(msg)),
489 };
490 let duration = match seconds_to_chrono_duration(seconds) {
491 Ok(d) => d,
492 Err(msg) => return OperationResult::Veto(Some(msg)),
493 };
494 let result_dt = time_aware + duration;
495 OperationResult::Value(Box::new(LiteralValue::time_with_type(
496 chrono_datetime_to_semantic_time(result_dt),
497 right.lemma_type.clone(),
498 )))
499 }
500
501 (ValueKind::Time(time), ValueKind::Date(date), ArithmeticComputation::Subtract) => {
502 let time_dt = match semantic_time_to_chrono_datetime(time) {
505 Ok(d) => d,
506 Err(msg) => return OperationResult::Veto(Some(msg)),
507 };
508
509 let naive_date = match NaiveDate::from_ymd_opt(date.year, date.month, date.day) {
511 Some(d) => d,
512 None => {
513 return OperationResult::Veto(Some(format!(
514 "Invalid date: {}-{}-{}",
515 date.year, date.month, date.day
516 )))
517 }
518 };
519 let naive_time = match NaiveTime::from_hms_opt(time.hour, time.minute, time.second) {
520 Some(t) => t,
521 None => {
522 return OperationResult::Veto(Some(format!(
523 "Invalid time: {}:{}:{}",
524 time.hour, time.minute, time.second
525 )))
526 }
527 };
528 let naive_dt = NaiveDateTime::new(naive_date, naive_time);
529
530 let offset = match create_semantic_timezone_offset(&time.timezone) {
532 Ok(o) => o,
533 Err(msg) => return OperationResult::Veto(Some(msg)),
534 };
535 let date_dt = match offset.from_local_datetime(&naive_dt).single() {
536 Some(dt) => dt,
537 None => {
538 return OperationResult::Veto(Some(
539 "Ambiguous or invalid datetime for timezone".to_string(),
540 ))
541 }
542 };
543
544 let duration = time_dt - date_dt;
545 let seconds = Decimal::from(duration.num_seconds());
546 OperationResult::Value(Box::new(LiteralValue::duration(
547 seconds,
548 SemanticDurationUnit::Second,
549 )))
550 }
551
552 _ => OperationResult::Veto(Some(format!(
553 "Time arithmetic operation {:?} not supported for these operand types",
554 op
555 ))),
556 }
557}
558
559fn semantic_time_to_chrono_datetime(time: &SemanticTime) -> Result<DateTime<FixedOffset>, String> {
560 let naive_date =
561 NaiveDate::from_ymd_opt(EPOCH_YEAR, EPOCH_MONTH, EPOCH_DAY).ok_or_else(|| {
562 format!(
563 "Invalid epoch date: {}-{}-{}",
564 EPOCH_YEAR, EPOCH_MONTH, EPOCH_DAY
565 )
566 })?;
567 let naive_time =
568 NaiveTime::from_hms_opt(time.hour, time.minute, time.second).ok_or_else(|| {
569 format!(
570 "Invalid time: {}:{}:{}",
571 time.hour, time.minute, time.second
572 )
573 })?;
574
575 let naive_dt = NaiveDateTime::new(naive_date, naive_time);
576
577 let offset = create_semantic_timezone_offset(&time.timezone)?;
578 offset
579 .from_local_datetime(&naive_dt)
580 .single()
581 .ok_or_else(|| "Ambiguous or invalid time for timezone".to_string())
582}
583
584fn chrono_datetime_to_semantic_time(dt: DateTime<FixedOffset>) -> SemanticTime {
585 let offset_seconds = dt.offset().local_minus_utc();
586 let offset_hours = (offset_seconds / SECONDS_PER_HOUR) as i8;
587 let offset_minutes = ((offset_seconds.abs() % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE) as u8;
588
589 SemanticTime {
590 hour: dt.hour(),
591 minute: dt.minute(),
592 second: dt.second(),
593 timezone: Some(SemanticTimezone {
594 offset_hours,
595 offset_minutes,
596 }),
597 }
598}