1use std::{fmt::Display, sync::Arc};
19
20use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeDelta};
21use miette::Diagnostic;
22use smol_str::SmolStr;
23use thiserror::Error;
24
25use crate::{
26 ast::{
27 CallStyle, Extension, ExtensionFunction, ExtensionOutputValue, ExtensionValue, Literal,
28 Name, RepresentableExtensionValue, RestrictedExpr, Type, Value, ValueKind,
29 },
30 entities::SchemaType,
31 evaluator::{self, EvaluationError},
32};
33
34const DATETIME_EXTENSION_NAME: &str = "datetime";
35
36#[allow(clippy::expect_used, clippy::unwrap_used)]
38mod constants {
39 use regex::Regex;
40 use std::sync::LazyLock;
41
42 use crate::{ast::Name, extensions::datetime::DATETIME_EXTENSION_NAME};
43
44 pub static DATETIME_CONSTRUCTOR_NAME: LazyLock<Name> = LazyLock::new(|| {
45 Name::parse_unqualified_name(DATETIME_EXTENSION_NAME).expect("should be a valid identifier")
46 });
47 pub static DURATION_CONSTRUCTOR_NAME: LazyLock<Name> = LazyLock::new(|| {
48 Name::parse_unqualified_name("duration").expect("should be a valid identifier")
49 });
50 pub static OFFSET_METHOD_NAME: LazyLock<Name> = LazyLock::new(|| {
51 Name::parse_unqualified_name("offset").expect("should be a valid identifier")
52 });
53 pub static DURATION_SINCE_NAME: LazyLock<Name> = LazyLock::new(|| {
54 Name::parse_unqualified_name("durationSince").expect("should be a valid identifier")
55 });
56 pub static TO_DATE_NAME: LazyLock<Name> = LazyLock::new(|| {
57 Name::parse_unqualified_name("toDate").expect("should be a valid identifier")
58 });
59 pub static TO_TIME_NAME: LazyLock<Name> = LazyLock::new(|| {
60 Name::parse_unqualified_name("toTime").expect("should be a valid identifier")
61 });
62 pub static TO_MILLISECONDS_NAME: LazyLock<Name> = LazyLock::new(|| {
63 Name::parse_unqualified_name("toMilliseconds").expect("should be a valid identifier")
64 });
65 pub static TO_SECONDS_NAME: LazyLock<Name> = LazyLock::new(|| {
66 Name::parse_unqualified_name("toSeconds").expect("should be a valid identifier")
67 });
68 pub static TO_MINUTES_NAME: LazyLock<Name> = LazyLock::new(|| {
69 Name::parse_unqualified_name("toMinutes").expect("should be a valid identifier")
70 });
71 pub static TO_HOURS_NAME: LazyLock<Name> = LazyLock::new(|| {
72 Name::parse_unqualified_name("toHours").expect("should be a valid identifier")
73 });
74 pub static TO_DAYS_NAME: LazyLock<Name> = LazyLock::new(|| {
75 Name::parse_unqualified_name("toDays").expect("should be a valid identifier")
76 });
77
78 pub static DURATION_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
81 Regex::new(r"^-?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$").unwrap()
82 });
83 pub static DATE_PATTERN: LazyLock<Regex> =
84 LazyLock::new(|| Regex::new(r"^([0-9]{4})-([0-9]{2})-([0-9]{2})").unwrap());
85 pub static HMS_PATTERN: LazyLock<Regex> =
86 LazyLock::new(|| Regex::new(r"^T([0-9]{2}):([0-9]{2}):([0-9]{2})").unwrap());
87 pub static MS_AND_OFFSET_PATTERN: LazyLock<Regex> =
88 LazyLock::new(|| Regex::new(r"^(\.([0-9]{3}))?(Z|((\+|-)([0-9]{2})([0-9]{2})))$").unwrap());
89}
90
91#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
93struct DateTime {
94 epoch: i64,
96}
97
98fn extension_err(
99 msg: String,
100 extension_name: &crate::ast::Name,
101 advice: Option<String>,
102) -> evaluator::EvaluationError {
103 evaluator::EvaluationError::failed_extension_function_application(
104 extension_name.clone(),
105 msg,
106 None, advice,
108 )
109}
110
111fn construct_from_str<Ext>(
112 arg: &Value,
113 constructor_name: Name,
114 constructor: impl Fn(&str) -> Result<Ext, EvaluationError>,
115) -> evaluator::Result<ExtensionOutputValue>
116where
117 Ext: ExtensionValue + std::cmp::Ord + 'static + std::clone::Clone,
118{
119 let s = arg.get_as_string()?;
120 let ext_value: Ext = constructor(s)?;
121 let arg_source_loc = arg.source_loc();
122 let e = RepresentableExtensionValue::new(
123 Arc::new(ext_value),
124 constructor_name,
125 vec![arg.clone().into()],
126 );
127 Ok(Value {
128 value: ValueKind::ExtensionValue(Arc::new(e)),
129 loc: arg_source_loc.cloned(), }
131 .into())
132}
133
134fn datetime_from_str(arg: &Value) -> evaluator::Result<ExtensionOutputValue> {
137 construct_from_str(arg, constants::DATETIME_CONSTRUCTOR_NAME.clone(), |s| {
138 parse_datetime(s).map(DateTime::from).map_err(|err| {
139 extension_err(
140 err.to_string(),
141 &constants::DATETIME_CONSTRUCTOR_NAME,
142 err.help().map(|v| v.to_string()),
143 )
144 })
145 })
146}
147
148fn as_ext<'a, Ext>(v: &'a Value, type_name: &'a Name) -> Result<&'a Ext, evaluator::EvaluationError>
149where
150 Ext: ExtensionValue + std::cmp::Ord + 'static,
151{
152 match &v.value {
153 ValueKind::ExtensionValue(ev) if ev.typename() == *type_name => {
154 #[allow(clippy::expect_used)]
156 let ext = ev
157 .value()
158 .as_any()
159 .downcast_ref::<Ext>()
160 .expect("already typechecked, so this downcast should succeed");
161 Ok(ext)
162 }
163 ValueKind::Lit(Literal::String(_)) => {
164 Err(evaluator::EvaluationError::type_error_with_advice_single(
165 Type::Extension {
166 name: type_name.to_owned(),
167 },
168 v,
169 format!("maybe you forgot to apply the `{type_name}` constructor?"),
170 ))
171 }
172 _ => Err(evaluator::EvaluationError::type_error_single(
173 Type::Extension {
174 name: type_name.to_owned(),
175 },
176 v,
177 )),
178 }
179}
180
181fn as_datetime(v: &Value) -> Result<DateTime, evaluator::EvaluationError> {
183 as_ext(v, &constants::DATETIME_CONSTRUCTOR_NAME).copied()
184}
185
186fn as_duration(v: &Value) -> Result<Duration, evaluator::EvaluationError> {
188 as_ext(v, &constants::DURATION_CONSTRUCTOR_NAME).copied()
189}
190
191fn offset(datetime: &Value, duration: &Value) -> evaluator::Result<ExtensionOutputValue> {
192 let datetime = as_datetime(datetime)?;
193 let duration = as_duration(duration)?;
194 let ret = datetime.offset(duration).ok_or_else(|| {
195 extension_err(
196 format!(
197 "overflows when adding an offset: {}+({})",
198 RestrictedExpr::from(datetime),
199 duration
200 ),
201 &constants::OFFSET_METHOD_NAME,
202 None,
203 )
204 })?;
205 Ok(Value {
206 value: ValueKind::ExtensionValue(Arc::new(ret.into())),
207 loc: None,
208 }
209 .into())
210}
211
212fn duration_since(lhs: &Value, rhs: &Value) -> evaluator::Result<ExtensionOutputValue> {
213 let lhs = as_datetime(lhs)?;
214 let rhs = as_datetime(rhs)?;
215 let ret = lhs.duration_since(rhs).ok_or_else(|| {
216 extension_err(
217 format!(
218 "overflows when computing the duration between {} and {}",
219 RestrictedExpr::from(lhs),
220 RestrictedExpr::from(rhs)
221 ),
222 &constants::DURATION_SINCE_NAME,
223 None,
224 )
225 })?;
226 Ok(Value {
227 value: ValueKind::ExtensionValue(Arc::new(ret.into())),
228 loc: None,
229 }
230 .into())
231}
232
233fn to_date(value: &Value) -> evaluator::Result<ExtensionOutputValue> {
234 let d = as_datetime(value)?;
235 let ret = d.to_date().ok_or_else(|| {
236 extension_err(
237 format!(
238 "overflows when computing the date of {}",
239 RestrictedExpr::from(d),
240 ),
241 &constants::TO_DATE_NAME,
242 None,
243 )
244 })?;
245 Ok(Value {
246 value: ValueKind::ExtensionValue(Arc::new(ret.into())),
247 loc: None,
248 }
249 .into())
250}
251
252fn to_time(value: &Value) -> evaluator::Result<ExtensionOutputValue> {
253 let d = as_datetime(value)?;
254 let ret = d.to_time();
255 Ok(Value {
256 value: ValueKind::ExtensionValue(Arc::new(ret.into())),
257 loc: None,
258 }
259 .into())
260}
261
262impl ExtensionValue for DateTime {
263 fn typename(&self) -> crate::ast::Name {
264 constants::DATETIME_CONSTRUCTOR_NAME.to_owned()
265 }
266 fn supports_operator_overloading(&self) -> bool {
267 true
268 }
269}
270
271impl DateTime {
272 const DAY_IN_MILLISECONDS: i64 = 1000 * 3600 * 24;
273 const UNIX_EPOCH_STR: &'static str = "1970-01-01";
274
275 fn offset(self, duration: Duration) -> Option<Self> {
276 self.epoch
277 .checked_add(duration.ms)
278 .map(|epoch| Self { epoch })
279 }
280
281 fn duration_since(self, other: DateTime) -> Option<Duration> {
282 self.epoch
283 .checked_sub(other.epoch)
284 .map(|ms| Duration { ms })
285 }
286
287 fn to_date(self) -> Option<Self> {
290 if self.epoch.is_negative() {
291 if self.epoch % Self::DAY_IN_MILLISECONDS == 0 {
292 Some(self.epoch)
293 } else {
294 (self.epoch / Self::DAY_IN_MILLISECONDS - 1).checked_mul(Self::DAY_IN_MILLISECONDS)
295 }
296 } else {
297 Some((self.epoch / Self::DAY_IN_MILLISECONDS) * Self::DAY_IN_MILLISECONDS)
298 }
299 .map(|epoch| Self { epoch })
300 }
301
302 fn to_time(self) -> Duration {
303 Duration {
304 ms: if self.epoch.is_negative() {
305 let rem = self.epoch % Self::DAY_IN_MILLISECONDS;
306 if rem == 0 {
307 rem
308 } else {
309 rem + Self::DAY_IN_MILLISECONDS
310 }
311 } else {
312 self.epoch % Self::DAY_IN_MILLISECONDS
313 },
314 }
315 }
316
317 fn as_ext_func_call(self) -> (Name, Vec<RestrictedExpr>) {
318 (
319 constants::OFFSET_METHOD_NAME.clone(),
320 vec![
321 RestrictedExpr::call_extension_fn(
322 constants::DATETIME_CONSTRUCTOR_NAME.clone(),
323 vec![Value::from(DateTime::UNIX_EPOCH_STR).into()],
324 ),
325 Duration { ms: self.epoch }.into(),
326 ],
327 )
328 }
329}
330
331impl From<DateTime> for RestrictedExpr {
332 fn from(value: DateTime) -> Self {
333 let (func, args) = value.as_ext_func_call();
334 Self::call_extension_fn(func, args)
335 }
336}
337
338impl From<DateTime> for RepresentableExtensionValue {
339 fn from(value: DateTime) -> Self {
340 let (func, args) = value.as_ext_func_call();
341 Self {
342 func,
343 args,
344 value: Arc::new(value),
345 }
346 }
347}
348
349impl From<NaiveDateTime> for DateTime {
350 fn from(value: NaiveDateTime) -> Self {
351 let delta = chrono::DateTime::from_naive_utc_and_offset(value, chrono::Utc)
352 - chrono::DateTime::UNIX_EPOCH;
353 Self {
354 epoch: delta.num_milliseconds(),
355 }
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
361struct Duration {
362 ms: i64,
364}
365
366impl Display for Duration {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 write!(f, "{}ms", self.ms)
369 }
370}
371
372impl ExtensionValue for Duration {
373 fn typename(&self) -> crate::ast::Name {
374 constants::DURATION_CONSTRUCTOR_NAME.to_owned()
375 }
376 fn supports_operator_overloading(&self) -> bool {
377 true
378 }
379}
380
381impl From<Duration> for RestrictedExpr {
382 fn from(value: Duration) -> Self {
383 let (func, args) = value.as_ext_func_call();
384 RestrictedExpr::call_extension_fn(func, args)
385 }
386}
387
388impl From<Duration> for RepresentableExtensionValue {
389 fn from(value: Duration) -> Self {
390 let (func, args) = value.as_ext_func_call();
391 Self {
392 func,
393 args,
394 value: Arc::new(value),
395 }
396 }
397}
398
399fn duration_from_str(arg: &Value) -> evaluator::Result<ExtensionOutputValue> {
402 construct_from_str(arg, constants::DURATION_CONSTRUCTOR_NAME.clone(), |s| {
403 parse_duration(s).map_err(|err| {
404 extension_err(
405 err.to_string(),
406 &constants::DURATION_CONSTRUCTOR_NAME,
407 err.help().map(|v| v.to_string()),
408 )
409 })
410 })
411}
412
413impl Duration {
414 fn to_milliseconds(self) -> i64 {
415 self.ms
416 }
417
418 fn to_seconds(self) -> i64 {
419 self.to_milliseconds() / 1000
420 }
421
422 fn to_minutes(self) -> i64 {
423 self.to_seconds() / 60
424 }
425
426 fn to_hours(self) -> i64 {
427 self.to_minutes() / 60
428 }
429
430 fn to_days(self) -> i64 {
431 self.to_hours() / 24
432 }
433
434 fn as_ext_func_call(self) -> (Name, Vec<RestrictedExpr>) {
435 (
436 constants::DURATION_CONSTRUCTOR_NAME.clone(),
437 vec![Value::from(self.to_string()).into()],
438 )
439 }
440}
441
442fn duration_method(
443 value: &Value,
444 internal_func: impl Fn(Duration) -> i64,
445) -> evaluator::Result<ExtensionOutputValue> {
446 let d = as_duration(value)?;
447 Ok(Value::from(internal_func(d)).into())
448}
449
450#[derive(Debug, Clone, Error, Diagnostic)]
451enum DurationParseError {
452 #[error("invalid duration pattern")]
453 #[help("A valid duration string is a concatenated sequence of quantity-unit pairs with an optional `-` at the beginning")]
454 InvalidPattern,
455 #[error("Duration overflows internal representation")]
456 #[help("A duration in milliseconds must be representable by a signed 64 bit integer")]
457 Overflow,
458}
459
460fn parse_duration(s: &str) -> Result<Duration, DurationParseError> {
461 if s.is_empty() || s == "-" {
462 return Err(DurationParseError::InvalidPattern);
463 }
464 let captures = constants::DURATION_PATTERN
465 .captures(s)
466 .ok_or(DurationParseError::InvalidPattern)?;
467 let get_number = |idx| {
468 captures
469 .get(idx)
470 .map_or(Some(0), |m| m.as_str().parse().ok())
471 .ok_or(DurationParseError::Overflow)
472 };
473 let d: u64 = get_number(2)?;
474 let h: u64 = get_number(4)?;
475 let m: u64 = get_number(6)?;
476 let sec: u64 = get_number(8)?;
477 let ms: u64 = get_number(10)?;
478 let checked_op = |x, y: u64, mul| {
479 (if s.starts_with('-') {
480 i64::checked_sub
481 } else {
482 i64::checked_add
483 })(
484 x,
485 i64::checked_mul(y.try_into().map_err(|_| DurationParseError::Overflow)?, mul)
486 .ok_or(DurationParseError::Overflow)?,
487 )
488 .ok_or(DurationParseError::Overflow)
489 };
490 let mut ms = if s.starts_with('-') {
491 i64::try_from(-i128::from(ms)).map_err(|_| DurationParseError::Overflow)?
492 } else {
493 i64::try_from(ms).map_err(|_| DurationParseError::Overflow)?
494 };
495 ms = checked_op(ms, sec, 1000)?;
496 ms = checked_op(ms, m, 1000 * 60)?;
497 ms = checked_op(ms, h, 1000 * 60 * 60)?;
498 ms = checked_op(ms, d, 1000 * 60 * 60 * 24)?;
499 Ok(Duration { ms })
500}
501
502#[derive(Debug, Clone, Error, Diagnostic)]
503enum DateTimeParseError {
504 #[error("invalid date pattern")]
505 #[help("A valid datetime string should start with YYYY-MM-DD")]
506 InvalidDatePattern,
507 #[error("invalid date: {0}")]
508 InvalidDate(SmolStr),
509 #[error("invalid hour/minute/second pattern")]
510 #[help("A valid datetime string should have HH:MM:SS after the date")]
511 InvalidHMSPattern,
512 #[error("invalid hour/minute/second: {0}")]
513 InvalidHMS(SmolStr),
514 #[error("invalid millisecond and/or offset pattern")]
515 #[help("A valid datetime should end with Z|.SSSZ|(+|-)hhmm|.SSS(+|-)hhmm")]
516 InvalidMSOffsetPattern,
517 #[error("invalid offset range: {}{}", ._0.0, ._0.1)]
518 #[help("A valid offset hour range should be [0,24) and minute range should be [0, 60)")]
519 InvalidOffset((u32, u32)),
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
523struct UTCOffset {
524 positive: bool,
525 hh: u32,
526 mm: u32,
527}
528
529impl UTCOffset {
530 const MAX_HH: u32 = 24;
531 const MAX_MM: u32 = 60;
532
533 fn to_seconds(&self) -> i64 {
534 let offset_in_seconds_unsigned = i64::from(self.hh * 3600 + self.mm * 60);
535 if self.positive {
536 offset_in_seconds_unsigned
537 } else {
538 -offset_in_seconds_unsigned
539 }
540 }
541
542 fn is_valid(&self) -> bool {
543 self.hh < Self::MAX_HH && self.mm < Self::MAX_MM
544 }
545}
546
547impl PartialOrd for UTCOffset {
548 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
549 Some(self.cmp(other))
550 }
551}
552
553impl Ord for UTCOffset {
554 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
555 self.to_seconds().cmp(&other.to_seconds())
556 }
557}
558
559fn parse_datetime(s: &str) -> Result<NaiveDateTime, DateTimeParseError> {
560 let (date_str, [year, month, day]) = constants::DATE_PATTERN
562 .captures(s)
563 .ok_or(DateTimeParseError::InvalidDatePattern)?
564 .extract();
565
566 #[allow(clippy::unwrap_used)]
571 let date = || {
572 NaiveDate::from_ymd_opt(
573 year.parse().unwrap(),
574 month.parse().unwrap(),
575 day.parse().unwrap(),
576 )
577 .ok_or_else(|| DateTimeParseError::InvalidDate(date_str.into()))
578 };
579
580 if date_str.len() == s.len() {
582 #[allow(clippy::unwrap_used)]
584 return Ok(NaiveDateTime::new(
585 date()?,
586 NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
587 ));
588 }
589
590 let s = &s[date_str.len()..];
592
593 let (hms_str, [h, m, sec]) = constants::HMS_PATTERN
594 .captures(s)
595 .ok_or(DateTimeParseError::InvalidHMSPattern)?
596 .extract();
597 #[allow(clippy::unwrap_used)]
599 let (h, m, sec): (u32, u32, u32) =
600 (h.parse().unwrap(), m.parse().unwrap(), sec.parse().unwrap());
601
602 let s = &s[hms_str.len()..];
604 let captures = constants::MS_AND_OFFSET_PATTERN
605 .captures(s)
606 .ok_or(DateTimeParseError::InvalidMSOffsetPattern)?;
607 let ms: u32 = if captures.get(1).is_some() {
608 #[allow(clippy::unwrap_used)]
610 captures[2].parse().unwrap()
611 } else {
612 0
613 };
614
615 let date = date()?;
616 let time = NaiveTime::from_hms_milli_opt(h, m, sec, ms)
617 .ok_or_else(|| DateTimeParseError::InvalidHMS(hms_str[1..].into()))?;
618 let offset: Result<TimeDelta, DateTimeParseError> = if captures.get(4).is_some() {
619 let positive = &captures[5] == "+";
620 #[allow(clippy::unwrap_used)]
622 let (offset_hour, offset_min): (u32, u32) =
623 (captures[6].parse().unwrap(), captures[7].parse().unwrap());
624 let offset = UTCOffset {
625 positive,
626 hh: offset_hour,
627 mm: offset_min,
628 };
629 if offset.is_valid() {
630 let offset_in_secs = offset.to_seconds();
631 #[allow(clippy::unwrap_used)]
633 Ok(TimeDelta::new(-offset_in_secs, 0).unwrap())
634 } else {
635 Err(DateTimeParseError::InvalidOffset((offset_hour, offset_min)))
636 }
637 } else {
638 Ok(TimeDelta::default())
639 };
640 Ok(NaiveDateTime::new(date, time) + offset?)
641}
642
643pub fn extension() -> Extension {
645 let datetime_type = SchemaType::Extension {
646 name: constants::DATETIME_CONSTRUCTOR_NAME.to_owned(),
647 };
648 let duration_type = SchemaType::Extension {
649 name: constants::DURATION_CONSTRUCTOR_NAME.to_owned(),
650 };
651 Extension::new(
652 constants::DATETIME_CONSTRUCTOR_NAME.clone(),
653 vec![
654 ExtensionFunction::unary(
655 constants::DATETIME_CONSTRUCTOR_NAME.clone(),
656 CallStyle::FunctionStyle,
657 Box::new(datetime_from_str),
658 datetime_type.clone(),
659 SchemaType::String,
660 ),
661 ExtensionFunction::unary(
662 constants::DURATION_CONSTRUCTOR_NAME.clone(),
663 CallStyle::FunctionStyle,
664 Box::new(duration_from_str),
665 duration_type.clone(),
666 SchemaType::String,
667 ),
668 ExtensionFunction::binary(
669 constants::OFFSET_METHOD_NAME.clone(),
670 CallStyle::MethodStyle,
671 Box::new(offset),
672 datetime_type.clone(),
673 (datetime_type.clone(), duration_type.clone()),
674 ),
675 ExtensionFunction::binary(
676 constants::DURATION_SINCE_NAME.clone(),
677 CallStyle::MethodStyle,
678 Box::new(duration_since),
679 duration_type.clone(),
680 (datetime_type.clone(), duration_type.clone()),
681 ),
682 ExtensionFunction::unary(
683 constants::TO_DATE_NAME.clone(),
684 CallStyle::MethodStyle,
685 Box::new(to_date),
686 datetime_type.clone(),
687 datetime_type.clone(),
688 ),
689 ExtensionFunction::unary(
690 constants::TO_TIME_NAME.clone(),
691 CallStyle::MethodStyle,
692 Box::new(to_time),
693 duration_type.clone(),
694 datetime_type,
695 ),
696 ExtensionFunction::unary(
697 constants::TO_MILLISECONDS_NAME.clone(),
698 CallStyle::MethodStyle,
699 Box::new(|value| duration_method(value, Duration::to_milliseconds)),
700 SchemaType::Long,
701 duration_type.clone(),
702 ),
703 ExtensionFunction::unary(
704 constants::TO_SECONDS_NAME.clone(),
705 CallStyle::MethodStyle,
706 Box::new(|value| duration_method(value, Duration::to_seconds)),
707 SchemaType::Long,
708 duration_type.clone(),
709 ),
710 ExtensionFunction::unary(
711 constants::TO_MINUTES_NAME.clone(),
712 CallStyle::MethodStyle,
713 Box::new(|value| duration_method(value, Duration::to_minutes)),
714 SchemaType::Long,
715 duration_type.clone(),
716 ),
717 ExtensionFunction::unary(
718 constants::TO_HOURS_NAME.clone(),
719 CallStyle::MethodStyle,
720 Box::new(|value| duration_method(value, Duration::to_hours)),
721 SchemaType::Long,
722 duration_type.clone(),
723 ),
724 ExtensionFunction::unary(
725 constants::TO_DAYS_NAME.clone(),
726 CallStyle::MethodStyle,
727 Box::new(|value| duration_method(value, Duration::to_days)),
728 SchemaType::Long,
729 duration_type,
730 ),
731 ],
732 [
733 constants::DATETIME_CONSTRUCTOR_NAME.clone(),
734 constants::DURATION_CONSTRUCTOR_NAME.clone(),
735 ],
736 )
737}
738
739#[cfg(test)]
740#[allow(clippy::cognitive_complexity)]
741mod tests {
742 use std::{str::FromStr, sync::Arc};
743
744 use chrono::NaiveDateTime;
745 use cool_asserts::assert_matches;
746 use nonempty::nonempty;
747
748 use crate::{
749 ast::{Eid, EntityUID, EntityUIDEntry, Expr, Request, Type, Value, ValueKind},
750 entities::Entities,
751 evaluator::{EvaluationError, Evaluator},
752 extensions::{
753 datetime::{
754 constants::{
755 DURATION_CONSTRUCTOR_NAME, TO_DATE_NAME, TO_DAYS_NAME, TO_HOURS_NAME,
756 TO_MILLISECONDS_NAME, TO_MINUTES_NAME, TO_SECONDS_NAME, TO_TIME_NAME,
757 },
758 parse_datetime, parse_duration, DateTimeParseError, Duration,
759 },
760 Extensions,
761 },
762 parser::parse_expr,
763 };
764
765 use super::{constants::DATETIME_CONSTRUCTOR_NAME, DateTime};
766
767 #[test]
768 fn test_parse_pos() {
769 let s = "2024-10-15";
770 assert_eq!(
771 parse_datetime(s).unwrap(),
772 NaiveDateTime::from_str("2024-10-15T00:00:00").unwrap()
773 );
774 let s = "2024-10-15T11:38:02Z";
775 assert_eq!(
776 parse_datetime(s).unwrap(),
777 NaiveDateTime::from_str("2024-10-15T11:38:02").unwrap()
778 );
779 let s = "2024-10-15T11:38:02.101Z";
780 assert_eq!(
781 parse_datetime(s).unwrap(),
782 NaiveDateTime::from_str("2024-10-15T11:38:02.101").unwrap()
783 );
784 let s = "2024-10-15T11:38:02.101+1134";
785 assert_eq!(
786 parse_datetime(s).unwrap(),
787 NaiveDateTime::from_str("2024-10-15T00:04:02.101").unwrap()
788 );
789 let s = "2024-10-15T11:38:02.101-1134";
790 assert_eq!(
791 parse_datetime(s).unwrap(),
792 NaiveDateTime::from_str("2024-10-15T23:12:02.101").unwrap()
793 );
794 let s = "2024-10-15T11:38:02+1134";
795 assert_eq!(
796 parse_datetime(s).unwrap(),
797 NaiveDateTime::from_str("2024-10-15T00:04:02").unwrap()
798 );
799 let s = "2024-10-15T11:38:02-1134";
800 assert_eq!(
801 parse_datetime(s).unwrap(),
802 NaiveDateTime::from_str("2024-10-15T23:12:02").unwrap()
803 );
804 let s = "2024-10-15T23:59:00+2359";
805 assert_eq!(
806 parse_datetime(s).unwrap(),
807 NaiveDateTime::from_str("2024-10-15T00:00:00").unwrap()
808 );
809 let s = "2024-10-15T00:00:00-2359";
810 assert_eq!(
811 parse_datetime(s).unwrap(),
812 NaiveDateTime::from_str("2024-10-15T23:59:00").unwrap()
813 );
814 }
815
816 #[test]
817 fn test_parse_neg() {
818 for s in [
819 "",
820 "a",
821 "-",
822 "-1",
823 "11-12-13",
824 "1111-1x-20",
825 "2024-10-15Z",
826 "2024-10-15T11:38:02ZZ",
827 ] {
828 assert!(parse_datetime(s).is_err());
829 }
830
831 assert_matches!(
833 parse_datetime("0000-0a-01"),
834 Err(DateTimeParseError::InvalidDatePattern)
835 );
836 assert_matches!(
837 parse_datetime("10000-01-01"),
838 Err(DateTimeParseError::InvalidDatePattern)
839 );
840 assert_matches!(
841 parse_datetime("10000-01-01T00:00:00Z"),
842 Err(DateTimeParseError::InvalidDatePattern)
843 );
844 assert_matches!(parse_datetime("2024-00-01"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-00-01");
845 assert_matches!(parse_datetime("2024-01-00"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-01-00");
846 assert_matches!(parse_datetime("2024-02-30"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-02-30");
847 assert_matches!(parse_datetime("2025-02-29"), Err(DateTimeParseError::InvalidDate(s)) if s == "2025-02-29");
848 assert_matches!(parse_datetime("2024-20-01"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-20-01");
849 assert_matches!(parse_datetime("2024-01-32"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-01-32");
850 assert_matches!(parse_datetime("2024-01-99"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-01-99");
851 assert_matches!(parse_datetime("2024-04-31"), Err(DateTimeParseError::InvalidDate(s)) if s == "2024-04-31");
852
853 assert_matches!(
855 parse_datetime("2024-01-01T"),
856 Err(DateTimeParseError::InvalidHMSPattern)
857 );
858 assert_matches!(
859 parse_datetime("2024-01-01Ta"),
860 Err(DateTimeParseError::InvalidHMSPattern)
861 );
862 assert_matches!(
863 parse_datetime("2024-01-01T01:"),
864 Err(DateTimeParseError::InvalidHMSPattern)
865 );
866 assert_matches!(
867 parse_datetime("2024-01-01T01:02"),
868 Err(DateTimeParseError::InvalidHMSPattern)
869 );
870 assert_matches!(
871 parse_datetime("2024-01-01T01:02:0b"),
872 Err(DateTimeParseError::InvalidHMSPattern)
873 );
874 assert_matches!(
875 parse_datetime("2024-01-01T01::02:03"),
876 Err(DateTimeParseError::InvalidHMSPattern)
877 );
878 assert_matches!(
879 parse_datetime("2024-01-01T01::02::03"),
880 Err(DateTimeParseError::InvalidHMSPattern)
881 );
882 assert_matches!(parse_datetime("2024-01-01T31:02:03Z"), Err(DateTimeParseError::InvalidHMS(s)) if s == "31:02:03");
883 assert_matches!(parse_datetime("2024-01-01T01:60:03Z"), Err(DateTimeParseError::InvalidHMS(s)) if s == "01:60:03");
884 assert_matches!(parse_datetime("2016-12-31T23:59:60Z"), Err(DateTimeParseError::InvalidHMS(s)) if s == "23:59:60");
890 assert_matches!(parse_datetime("2016-12-31T23:59:61Z"), Err(DateTimeParseError::InvalidHMS(s)) if s == "23:59:61");
891
892 assert_matches!(
893 parse_datetime("2024-01-01T00:00:00"),
894 Err(DateTimeParseError::InvalidMSOffsetPattern)
895 );
896 assert_matches!(
897 parse_datetime("2024-01-01T00:00:00T"),
898 Err(DateTimeParseError::InvalidMSOffsetPattern)
899 );
900 assert_matches!(
901 parse_datetime("2024-01-01T00:00:00ZZ"),
902 Err(DateTimeParseError::InvalidMSOffsetPattern)
903 );
904 assert_matches!(
905 parse_datetime("2024-01-01T00:00:00x001Z"),
906 Err(DateTimeParseError::InvalidMSOffsetPattern)
907 );
908 assert_matches!(
909 parse_datetime("2024-01-01T00:00:00.001ZZ"),
910 Err(DateTimeParseError::InvalidMSOffsetPattern)
911 );
912 assert_matches!(
913 parse_datetime("2024-01-01T00:00:00➕0000"),
914 Err(DateTimeParseError::InvalidMSOffsetPattern)
915 );
916 assert_matches!(
917 parse_datetime("2024-01-01T00:00:00➖0000"),
918 Err(DateTimeParseError::InvalidMSOffsetPattern)
919 );
920 assert_matches!(
921 parse_datetime("2024-01-01T00:00:00.0001Z"),
922 Err(DateTimeParseError::InvalidMSOffsetPattern)
923 );
924 assert_matches!(
925 parse_datetime("2024-01-01T00:00:00.001➖0000"),
926 Err(DateTimeParseError::InvalidMSOffsetPattern)
927 );
928 assert_matches!(
929 parse_datetime("2024-01-01T00:00:00.001➕0000"),
930 Err(DateTimeParseError::InvalidMSOffsetPattern)
931 );
932 assert_matches!(
933 parse_datetime("2024-01-01T00:00:00.001+00000"),
934 Err(DateTimeParseError::InvalidMSOffsetPattern)
935 );
936 assert_matches!(
937 parse_datetime("2024-01-01T00:00:00.001-00000"),
938 Err(DateTimeParseError::InvalidMSOffsetPattern)
939 );
940 assert_matches!(
941 parse_datetime("2016-12-31T00:00:00+1160"),
942 Err(DateTimeParseError::InvalidOffset((11, 60)))
943 );
944 assert_matches!(
945 parse_datetime("2016-12-31T00:00:00+1199"),
946 Err(DateTimeParseError::InvalidOffset((11, 99)))
947 );
948 assert_matches!(
949 parse_datetime("2016-12-31T00:00:00+2400"),
950 Err(DateTimeParseError::InvalidOffset((24, 0)))
951 );
952 }
953
954 #[track_caller]
955 fn milliseconds_to_duration(ms: i128) -> String {
956 let sign = if ms < 0 { "-" } else { "" };
957 let mut ms = ms.abs();
958 let milliseconds = ms % 1000;
959 ms /= 1000;
960 let seconds = ms % 60;
961 ms /= 60;
962 let minutes = ms % 60;
963 ms /= 60;
964 let hours = ms % 24;
965 ms /= 24;
966 let days = ms;
967 format!("{sign}{days}d{hours}h{minutes}m{seconds}s{milliseconds}ms")
968 }
969
970 #[test]
971 fn parse_duration_pos() {
972 assert_eq!(parse_duration("1h").unwrap(), Duration { ms: 3600 * 1000 });
973 assert_eq!(
974 parse_duration("-10h").unwrap(),
975 Duration {
976 ms: -3600 * 10 * 1000
977 }
978 );
979 assert_eq!(
980 parse_duration("5d3ms").unwrap(),
981 Duration {
982 ms: 3600 * 24 * 5 * 1000 + 3
983 }
984 );
985 assert_eq!(
986 parse_duration("-3h5m").unwrap(),
987 Duration {
988 ms: -3600 * 3 * 1000 - 300 * 1000
989 }
990 );
991 assert!(parse_duration(&milliseconds_to_duration(i64::MAX.into())).is_ok());
992 assert!(parse_duration(&milliseconds_to_duration(i64::MIN.into())).is_ok());
993 }
994
995 #[test]
996 fn parse_duration_neg() {
997 for s in [
998 "", "a", "-", "-1", "➖1ms", "11dd", "00000mm", "-d", "-h", "-1hh", "-h2d", "-ms",
999 "1ms1s", "1ms1m", "1ms1h", "0ms1d", "1s1m", "1s1h", "0s0d", "0m0h", "0m0d", "1h1d",
1001 "1ms1m1d",
1002 ] {
1003 assert!(parse_duration(s).is_err());
1004 }
1005 assert!(parse_duration(&milliseconds_to_duration(i128::from(i64::MAX) + 1)).is_err());
1006 assert!(parse_duration(&milliseconds_to_duration(i128::from(i64::MIN) - 1)).is_err());
1007 }
1008
1009 #[test]
1010 fn test_offset() {
1011 let unix_epoch = DateTime { epoch: 0 };
1012 let date_time_max = unix_epoch
1013 .offset(parse_duration(&milliseconds_to_duration(i64::MAX.into())).unwrap())
1014 .expect("valid datetime");
1015 let date_time_min = unix_epoch
1016 .offset(parse_duration(&milliseconds_to_duration(i64::MIN.into())).unwrap())
1017 .expect("valid datetime");
1018 assert!(date_time_max
1019 .offset(parse_duration("1ms").unwrap())
1020 .is_none());
1021 assert_eq!(
1022 date_time_max.offset(parse_duration("-1ms").unwrap()),
1023 Some(
1024 unix_epoch
1025 .offset(
1026 parse_duration(&milliseconds_to_duration(i128::from(i64::MAX) - 1))
1027 .unwrap()
1028 )
1029 .expect("valid datetime")
1030 )
1031 );
1032 assert!(date_time_min
1033 .offset(parse_duration("-1ms").unwrap())
1034 .is_none());
1035 assert_eq!(
1036 date_time_min.offset(parse_duration("1ms").unwrap()),
1037 Some(
1038 unix_epoch
1039 .offset(
1040 parse_duration(&milliseconds_to_duration(i128::from(i64::MIN) + 1))
1041 .unwrap()
1042 )
1043 .expect("valid datetime")
1044 )
1045 );
1046 assert_eq!(
1047 unix_epoch.offset(parse_duration("1d").unwrap()),
1048 Some(parse_datetime("1970-01-02").unwrap().into())
1049 );
1050 assert_eq!(
1051 unix_epoch.offset(parse_duration("-1d").unwrap()),
1052 Some(parse_datetime("1969-12-31").unwrap().into())
1053 );
1054 }
1055
1056 #[test]
1057 fn test_duration_since() {
1058 let unix_epoch = DateTime { epoch: 0 };
1059 let today: DateTime = parse_datetime("2024-10-24").unwrap().into();
1060 assert_eq!(
1061 today.duration_since(unix_epoch),
1062 Some(parse_duration("20020d").unwrap())
1063 );
1064 let yesterday: DateTime = parse_datetime("2024-10-23").unwrap().into();
1065 assert_eq!(
1066 yesterday.duration_since(today),
1067 Some(parse_duration("-1d").unwrap())
1068 );
1069 assert_eq!(
1070 today.duration_since(yesterday),
1071 Some(parse_duration("1d").unwrap())
1072 );
1073
1074 let date_time_min = unix_epoch
1075 .offset(parse_duration(&milliseconds_to_duration(i64::MIN.into())).unwrap())
1076 .expect("valid datetime");
1077 assert!(today.duration_since(date_time_min).is_none());
1078 }
1079
1080 #[test]
1081 fn test_to_date() {
1082 let unix_epoch = DateTime { epoch: 0 };
1083 let today: DateTime = parse_datetime("2024-10-24").unwrap().into();
1084 assert_eq!(
1085 today.duration_since(unix_epoch),
1086 Some(parse_duration("20020d").unwrap())
1087 );
1088 let yesterday: DateTime = parse_datetime("2024-10-23").unwrap().into();
1089 assert_eq!(
1090 yesterday.duration_since(today),
1091 Some(parse_duration("-1d").unwrap())
1092 );
1093 let some_day_before_unix_epoch: DateTime = parse_datetime("1900-01-01").unwrap().into();
1094
1095 let max_day_offset = parse_duration("23h59m59s999ms").unwrap();
1096 let min_day_offset = parse_duration("-23h59m59s999ms").unwrap();
1097
1098 for d in [today, yesterday, unix_epoch, some_day_before_unix_epoch] {
1099 assert_eq!(d.to_date().expect("should not overflow"), d);
1100 assert_eq!(
1101 d.offset(max_day_offset)
1102 .unwrap()
1103 .to_date()
1104 .expect("should not overflow"),
1105 d
1106 );
1107 assert_eq!(
1108 d.offset(min_day_offset)
1109 .unwrap()
1110 .to_date()
1111 .expect("should not overflow"),
1112 d.offset(parse_duration("-1d").unwrap()).unwrap()
1113 );
1114 }
1115
1116 assert!(unix_epoch
1117 .offset(Duration { ms: i64::MIN })
1118 .expect("should be able to construct")
1119 .to_date()
1120 .is_none());
1121 }
1122
1123 #[test]
1124 fn test_to_time() {
1125 let unix_epoch = DateTime { epoch: 0 };
1126 let today: DateTime = parse_datetime("2024-10-24").unwrap().into();
1127 assert_eq!(
1128 today.duration_since(unix_epoch),
1129 Some(parse_duration("20020d").unwrap())
1130 );
1131 let yesterday: DateTime = parse_datetime("2024-10-23").unwrap().into();
1132 assert_eq!(
1133 yesterday.duration_since(today),
1134 Some(parse_duration("-1d").unwrap())
1135 );
1136 let some_day_before_unix_epoch: DateTime = parse_datetime("1900-01-01").unwrap().into();
1137
1138 let max_day_offset = parse_duration("23h59m59s999ms").unwrap();
1139 let min_day_offset = parse_duration("-23h59m59s999ms").unwrap();
1140
1141 for d in [today, yesterday, unix_epoch, some_day_before_unix_epoch] {
1142 assert_eq!(d.offset(max_day_offset).unwrap().to_time(), max_day_offset);
1143 assert_eq!(
1144 d.offset(min_day_offset).unwrap().to_time(),
1145 parse_duration("1ms").unwrap(),
1146 );
1147 }
1148 }
1149
1150 #[test]
1151 fn test_predicates() {
1152 let unix_epoch = DateTime { epoch: 0 };
1153 let today: DateTime = parse_datetime("2024-10-24").unwrap().into();
1154 let yesterday: DateTime = parse_datetime("2024-10-23").unwrap().into();
1155 let some_day_before_unix_epoch: DateTime = parse_datetime("1900-01-01").unwrap().into();
1156 assert!(unix_epoch <= unix_epoch);
1157 assert!(today == today);
1158 assert!(today != yesterday);
1159 assert!(unix_epoch < today);
1160 assert!(today > yesterday);
1161 assert!(some_day_before_unix_epoch <= unix_epoch);
1162 assert!(today >= some_day_before_unix_epoch);
1163 assert!(yesterday >= some_day_before_unix_epoch);
1164 }
1165
1166 #[test]
1167 fn test_duration_methods() {
1168 let day_offset = parse_duration("10d23h59m58s999ms").unwrap();
1169 let day_offset_neg = parse_duration("-10d23h59m58s999ms").unwrap();
1170 for o in [day_offset, day_offset_neg] {
1171 assert_eq!(o.to_days().abs(), 10);
1172 assert_eq!(o.to_hours().abs(), 10 * 24 + 23);
1173 assert_eq!(o.to_minutes().abs(), (10 * 24 + 23) * 60 + 59);
1174 assert_eq!(o.to_seconds().abs(), ((10 * 24 + 23) * 60 + 59) * 60 + 58);
1175 assert_eq!(
1176 o.to_milliseconds().abs(),
1177 (((10 * 24 + 23) * 60 + 59) * 60 + 58) * 1000 + 999
1178 );
1179 }
1180 }
1181
1182 fn dummy_entity() -> EntityUIDEntry {
1183 EntityUIDEntry::Known {
1184 euid: Arc::new(EntityUID::from_components(
1185 "A".parse().unwrap(),
1186 Eid::new(""),
1187 None,
1188 )),
1189 loc: None,
1190 }
1191 }
1192
1193 #[test]
1194 fn test_interpretation_datetime() {
1195 let dummy_entity = dummy_entity();
1196 let entities = Entities::default();
1197 let eval = Evaluator::new(
1198 Request::new_unchecked(
1199 dummy_entity.clone(),
1200 dummy_entity.clone(),
1201 dummy_entity,
1202 None,
1203 ),
1204 &entities,
1205 Extensions::all_available(),
1206 );
1207
1208 assert_matches!(
1209 eval.interpret_inline_policy(&Expr::call_extension_fn(
1210 DATETIME_CONSTRUCTOR_NAME.clone(),
1211 vec![Value::from("2024-10-28").into()]
1212 )),
1213 Ok(Value {
1214 value: ValueKind::ExtensionValue(ext),
1215 ..
1216 }) => {
1217 assert!(ext.value().equals_extvalue(&DateTime {epoch: 1730073600000}));
1218 }
1219 );
1220
1221 assert_matches!(
1222 eval.interpret_inline_policy(&Expr::call_extension_fn(
1223 DATETIME_CONSTRUCTOR_NAME.clone(),
1224 vec![Value::from("2024-10-28T01:22:33.456Z").into()]
1225 )),
1226 Ok(Value {
1227 value: ValueKind::ExtensionValue(ext),
1228 ..
1229 }) => {
1230 assert!(ext.value().equals_extvalue(&DateTime {epoch: 1730078553456}));
1231 }
1232 );
1233
1234 assert_matches!(
1235 eval.interpret_inline_policy(&Expr::call_extension_fn(
1236 DATETIME_CONSTRUCTOR_NAME.clone(),
1237 vec![Value::from("2024-10-28T10:12:13.456-0700").into()]
1238 )),
1239 Ok(Value {
1240 value: ValueKind::ExtensionValue(ext),
1241 ..
1242 }) => {
1243 assert!(ext.value().equals_extvalue(&DateTime {epoch: 1730135533456}));
1244 }
1245 );
1246
1247 assert_matches!(
1248 eval.interpret_inline_policy(&Expr::call_extension_fn(
1249 DATETIME_CONSTRUCTOR_NAME.clone(),
1250 vec![Value::from("22024-30-28T10:12:13.456-0700").into()]
1251 )),
1252 Err(EvaluationError::FailedExtensionFunctionExecution(err)) => {
1253 assert_eq!(err.extension_name, *DATETIME_CONSTRUCTOR_NAME);
1254 assert_eq!(err.msg, "invalid date pattern".to_owned());
1255 assert_eq!(err.advice, None);
1257 }
1258 );
1259
1260 assert_eq!(
1262 eval.interpret_inline_policy(
1263 &parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").offset(duration("-7h"))"#)
1264 .unwrap()
1265 )
1266 .unwrap(),
1267 eval.interpret_inline_policy(
1268 &parse_expr(r#"datetime("2024-10-28T10:12:13.456Z")"#).unwrap()
1269 )
1270 .unwrap()
1271 );
1272 assert_eq!(
1273 eval.interpret_inline_policy(
1274 &parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").offset(duration("7h"))"#)
1275 .unwrap()
1276 )
1277 .unwrap(),
1278 eval.interpret_inline_policy(
1279 &parse_expr(r#"datetime("2024-10-28T10:12:13.456Z")"#).unwrap()
1280 )
1281 .unwrap()
1282 );
1283
1284 assert_matches!(
1285 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").offset("7h")"#).unwrap()),
1286 Err(EvaluationError::TypeError(err)) => {
1287 assert_eq!(err.expected, nonempty![Type::Extension { name: DURATION_CONSTRUCTOR_NAME.clone() }]);
1288 assert_eq!(err.actual, Type::String);
1289 assert_eq!(err.advice, Some("maybe you forgot to apply the `duration` constructor?".to_owned()));
1290 }
1291 );
1292
1293 assert_eq!(
1295 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").durationSince(datetime("2024-10-28T10:12:13.456Z"))"#).unwrap()).unwrap(),
1296 eval.interpret_inline_policy(&parse_expr(r#"duration("-7h")"#).unwrap()).unwrap()
1297 );
1298
1299 assert_eq!(
1300 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").durationSince(datetime("2024-10-28T10:12:13.456Z"))"#).unwrap()).unwrap(),
1301 eval.interpret_inline_policy(&parse_expr(r#"duration("7h")"#).unwrap()).unwrap()
1302 );
1303
1304 assert_matches!(
1305 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").durationSince("7h")"#).unwrap()),
1306 Err(EvaluationError::TypeError(err)) => {
1307 assert_eq!(err.expected, nonempty![Type::Extension { name: DATETIME_CONSTRUCTOR_NAME.clone() }]);
1308 assert_eq!(err.actual, Type::String);
1309 assert_eq!(err.advice, Some("maybe you forgot to apply the `datetime` constructor?".to_owned()));
1310 }
1311 );
1312
1313 assert_eq!(
1315 eval.interpret_inline_policy(
1316 &parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").toDate()"#).unwrap()
1317 )
1318 .unwrap(),
1319 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28")"#).unwrap())
1320 .unwrap()
1321 );
1322
1323 assert_eq!(
1324 eval.interpret_inline_policy(
1325 &parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").toDate()"#).unwrap()
1326 )
1327 .unwrap(),
1328 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28")"#).unwrap())
1329 .unwrap()
1330 );
1331
1332 assert_matches!(
1333 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").toDate(1)"#).unwrap()),
1334 Err(EvaluationError::WrongNumArguments(err)) => {
1335 assert_eq!(err.function_name, *TO_DATE_NAME);
1336 assert_eq!(err.actual, 2);
1337 assert_eq!(err.expected, 1);
1338 }
1339 );
1340
1341 assert_eq!(
1343 eval.interpret_inline_policy(
1344 &parse_expr(r#"datetime("2024-10-28T10:12:13.456+0700").toTime()"#).unwrap()
1345 )
1346 .unwrap(),
1347 eval.interpret_inline_policy(&parse_expr(r#"duration("3h12m13s456ms")"#).unwrap())
1348 .unwrap()
1349 );
1350
1351 assert_eq!(
1352 eval.interpret_inline_policy(
1353 &parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").toTime()"#).unwrap()
1354 )
1355 .unwrap(),
1356 eval.interpret_inline_policy(&parse_expr(r#"duration("17h12m13s456ms")"#).unwrap())
1357 .unwrap()
1358 );
1359
1360 assert_matches!(
1361 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700").toTime(1)"#).unwrap()),
1362 Err(EvaluationError::WrongNumArguments(err)) => {
1363 assert_eq!(err.function_name, *TO_TIME_NAME);
1364 assert_eq!(err.actual, 2);
1365 assert_eq!(err.expected, 1);
1366 }
1367 );
1368
1369 assert_eq!(
1371 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") > datetime("2024-10-28T10:12:13.456Z")"#).unwrap()).unwrap(),
1372 Value::from(true),
1373 );
1374 assert_eq!(
1375 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") >= datetime("2024-10-28T10:12:13.456Z")"#).unwrap()).unwrap(),
1376 Value::from(true),
1377 );
1378 assert_eq!(
1379 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") != datetime("2024-10-28T10:12:13.456Z")"#).unwrap()).unwrap(),
1380 Value::from(true),
1381 );
1382 assert_eq!(
1383 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") == datetime("2024-10-28T17:12:13.456Z")"#).unwrap()).unwrap(),
1384 Value::from(true),
1385 );
1386 assert_eq!(
1387 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") < datetime("2024-10-28T17:12:13.456-0800")"#).unwrap()).unwrap(),
1388 Value::from(true),
1389 );
1390 assert_eq!(
1391 eval.interpret_inline_policy(&parse_expr(r#"datetime("2024-10-28T10:12:13.456-0700") <= datetime("2024-10-28T17:12:13.456-0800")"#).unwrap()).unwrap(),
1392 Value::from(true),
1393 );
1394 }
1395
1396 #[test]
1397 fn test_interpretation_duration() {
1398 let dummy_entity = dummy_entity();
1399 let entities = Entities::default();
1400 let eval = Evaluator::new(
1401 Request::new_unchecked(
1402 dummy_entity.clone(),
1403 dummy_entity.clone(),
1404 dummy_entity,
1405 None,
1406 ),
1407 &entities,
1408 Extensions::all_available(),
1409 );
1410
1411 assert_matches!(
1412 eval.interpret_inline_policy(&Expr::call_extension_fn(
1413 DURATION_CONSTRUCTOR_NAME.clone(),
1414 vec![Value::from("1d2h3m4s50ms").into()]
1415 )),
1416 Ok(Value {
1417 value: ValueKind::ExtensionValue(ext),
1418 ..
1419 }) => {
1420 assert!(ext.value().equals_extvalue(&Duration {ms: 93784050}));
1421 }
1422 );
1423
1424 assert_matches!(
1425 eval.interpret_inline_policy(&Expr::call_extension_fn(
1426 DURATION_CONSTRUCTOR_NAME.clone(),
1427 vec![Value::from("1dd2h3m4s50ms").into()]
1428 )),
1429 Err(EvaluationError::FailedExtensionFunctionExecution(err)) => {
1430 assert_eq!(err.extension_name, *DURATION_CONSTRUCTOR_NAME);
1431 assert_eq!(err.msg, "invalid duration pattern".to_owned());
1432 assert_eq!(err.advice, None);
1434 }
1435 );
1436
1437 assert_eq!(
1439 eval.interpret_inline_policy(
1440 &parse_expr(r#"duration("1d2h3m4s50ms").toMilliseconds()"#).unwrap()
1441 )
1442 .unwrap(),
1443 Value::from(93784050)
1444 );
1445
1446 assert_eq!(
1447 eval.interpret_inline_policy(
1448 &parse_expr(r#"duration("-1d2h3m4s50ms").toMilliseconds()"#).unwrap()
1449 )
1450 .unwrap(),
1451 Value::from(-93784050)
1452 );
1453
1454 assert_matches!(
1455 eval.interpret_inline_policy(&parse_expr(r#"duration("-1d2h3m4s50ms").toMilliseconds(1)"#).unwrap()),
1456 Err(EvaluationError::WrongNumArguments(err)) => {
1457 assert_eq!(err.function_name, *TO_MILLISECONDS_NAME);
1458 assert_eq!(err.actual, 2);
1459 assert_eq!(err.expected, 1);
1460 }
1461 );
1462
1463 assert_eq!(
1465 eval.interpret_inline_policy(
1466 &parse_expr(r#"duration("1d2h3m4s50ms").toSeconds()"#).unwrap()
1467 )
1468 .unwrap(),
1469 Value::from(93784)
1470 );
1471
1472 assert_eq!(
1473 eval.interpret_inline_policy(
1474 &parse_expr(r#"duration("-1d2h3m4s50ms").toSeconds()"#).unwrap()
1475 )
1476 .unwrap(),
1477 Value::from(-93784)
1478 );
1479
1480 assert_matches!(
1481 eval.interpret_inline_policy(&parse_expr(r#"duration("-1d2h3m4s50ms").toSeconds(1)"#).unwrap()),
1482 Err(EvaluationError::WrongNumArguments(err)) => {
1483 assert_eq!(err.function_name, *TO_SECONDS_NAME);
1484 assert_eq!(err.actual, 2);
1485 assert_eq!(err.expected, 1);
1486 }
1487 );
1488
1489 assert_eq!(
1491 eval.interpret_inline_policy(
1492 &parse_expr(r#"duration("1d2h3m4s50ms").toMinutes()"#).unwrap()
1493 )
1494 .unwrap(),
1495 Value::from(1563)
1496 );
1497
1498 assert_eq!(
1499 eval.interpret_inline_policy(
1500 &parse_expr(r#"duration("-1d2h3m4s50ms").toMinutes()"#).unwrap()
1501 )
1502 .unwrap(),
1503 Value::from(-1563)
1504 );
1505
1506 assert_matches!(
1507 eval.interpret_inline_policy(&parse_expr(r#"duration("-1d2h3m4s50ms").toMinutes(1)"#).unwrap()),
1508 Err(EvaluationError::WrongNumArguments(err)) => {
1509 assert_eq!(err.function_name, *TO_MINUTES_NAME);
1510 assert_eq!(err.actual, 2);
1511 assert_eq!(err.expected, 1);
1512 }
1513 );
1514
1515 assert_eq!(
1517 eval.interpret_inline_policy(
1518 &parse_expr(r#"duration("1d2h3m4s50ms").toHours()"#).unwrap()
1519 )
1520 .unwrap(),
1521 Value::from(26)
1522 );
1523
1524 assert_eq!(
1525 eval.interpret_inline_policy(
1526 &parse_expr(r#"duration("-1d2h3m4s50ms").toHours()"#).unwrap()
1527 )
1528 .unwrap(),
1529 Value::from(-26)
1530 );
1531
1532 assert_matches!(
1533 eval.interpret_inline_policy(&parse_expr(r#"duration("-1d2h3m4s50ms").toHours(1)"#).unwrap()),
1534 Err(EvaluationError::WrongNumArguments(err)) => {
1535 assert_eq!(err.function_name, *TO_HOURS_NAME);
1536 assert_eq!(err.actual, 2);
1537 assert_eq!(err.expected, 1);
1538 }
1539 );
1540
1541 assert_eq!(
1543 eval.interpret_inline_policy(
1544 &parse_expr(r#"duration("1d2h3m4s50ms").toDays()"#).unwrap()
1545 )
1546 .unwrap(),
1547 Value::from(1)
1548 );
1549
1550 assert_eq!(
1551 eval.interpret_inline_policy(
1552 &parse_expr(r#"duration("-1d2h3m4s50ms").toDays()"#).unwrap()
1553 )
1554 .unwrap(),
1555 Value::from(-1)
1556 );
1557
1558 assert_matches!(
1559 eval.interpret_inline_policy(&parse_expr(r#"duration("-1d2h3m4s50ms").toDays(1)"#).unwrap()),
1560 Err(EvaluationError::WrongNumArguments(err)) => {
1561 assert_eq!(err.function_name, *TO_DAYS_NAME);
1562 assert_eq!(err.actual, 2);
1563 assert_eq!(err.expected, 1);
1564 }
1565 );
1566
1567 assert_eq!(
1569 eval.interpret_inline_policy(
1570 &parse_expr(r#"duration("-2h") < duration("1h")"#).unwrap()
1571 )
1572 .unwrap(),
1573 Value::from(true),
1574 );
1575 assert_eq!(
1576 eval.interpret_inline_policy(
1577 &parse_expr(r#"duration("-2h") <= duration("1h")"#).unwrap()
1578 )
1579 .unwrap(),
1580 Value::from(true),
1581 );
1582 assert_eq!(
1583 eval.interpret_inline_policy(
1584 &parse_expr(r#"duration("-2h") != duration("1h")"#).unwrap()
1585 )
1586 .unwrap(),
1587 Value::from(true),
1588 );
1589 assert_eq!(
1590 eval.interpret_inline_policy(
1591 &parse_expr(r#"duration("-3d") == duration("-72h")"#).unwrap()
1592 )
1593 .unwrap(),
1594 Value::from(true),
1595 );
1596 assert_eq!(
1597 eval.interpret_inline_policy(
1598 &parse_expr(r#"duration("2h") > duration("1h")"#).unwrap()
1599 )
1600 .unwrap(),
1601 Value::from(true),
1602 );
1603 assert_eq!(
1604 eval.interpret_inline_policy(
1605 &parse_expr(r#"duration("2h") >= duration("1h")"#).unwrap()
1606 )
1607 .unwrap(),
1608 Value::from(true),
1609 );
1610 }
1611}