use std::sync::Arc;
use chrono::{DateTime, SecondsFormat, Utc};
use crate::tree::ast::expression::{Expression, NullLiteral};
use crate::tree::ast::ops::BinaryOp;
use crate::tree::builder::{call, null, BinaryOperatorBuilder, ExpressionBuilder};
#[derive(Debug, Clone, Default)]
pub struct TimeRange {
pub start: Option<DateTime<Utc>>,
pub end: Option<DateTime<Utc>>,
pub end_inclusive: bool,
}
impl From<TimeRange> for Expression {
fn from(range: TimeRange) -> Self {
let ts_call =
|dt: DateTime<Utc>| call("ts").arg(dt.to_rfc3339_opts(SecondsFormat::Millis, true));
let range_op = if range.end_inclusive {
BinaryOp::RangeInclusive
} else {
BinaryOp::Range
};
match (range.start, range.end) {
(Some(start), Some(end)) => {
BinaryOperatorBuilder::new(range_op, ts_call(start), ts_call(end)).build()
}
(Some(start), None) => {
BinaryOperatorBuilder::new(range_op, ts_call(start), null()).build()
}
(None, Some(end)) => BinaryOperatorBuilder::new(range_op, null(), ts_call(end)).build(),
(None, None) => Expression::from_kind(NullLiteral),
}
}
}
impl From<TimeRange> for Arc<Expression> {
fn from(range: TimeRange) -> Self {
Arc::new(Expression::from(range))
}
}
#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
use super::*;
use crate::tree::ast::ParseWithErrors;
fn timestamp(hour: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(2024, 1, 1, hour, 0, 0)
.single()
.expect("valid test timestamp")
}
fn assert_expression(range: TimeRange, expected: &str) {
let actual = Expression::from(range);
let expected = Expression::parse_result(expected).expect("valid expected expression");
assert_eq!(actual, expected);
}
#[test]
fn exclusive_range_uses_exclusive_operator_by_default() {
assert_expression(
TimeRange {
start: Some(timestamp(1)),
end: Some(timestamp(2)),
..Default::default()
},
"ts('2024-01-01T01:00:00.000Z')..ts('2024-01-01T02:00:00.000Z')",
);
}
#[test]
fn inclusive_range_uses_inclusive_operator() {
assert_expression(
TimeRange {
start: Some(timestamp(1)),
end: Some(timestamp(2)),
end_inclusive: true,
},
"ts('2024-01-01T01:00:00.000Z')..=ts('2024-01-01T02:00:00.000Z')",
);
}
#[test]
fn zero_width_inclusive_range_is_preserved() {
assert_expression(
TimeRange {
start: Some(timestamp(1)),
end: Some(timestamp(1)),
end_inclusive: true,
},
"ts('2024-01-01T01:00:00.000Z')..=ts('2024-01-01T01:00:00.000Z')",
);
}
}