use lemma::{DateGranularity, DateTimeValue, Engine, TimezoneValue, ValueKind};
use std::collections::HashMap;
fn effective(y: i32, m: u32, d: u32, h: u32, min: u32, s: u32) -> DateTimeValue {
DateTimeValue {
year: y,
month: m,
day: d,
hour: h,
minute: min,
second: s,
microsecond: 0,
timezone: Some(TimezoneValue {
offset_hours: 0,
offset_minutes: 0,
}),
granularity: DateGranularity::DateTime,
}
}
fn effective_us(y: i32, m: u32, d: u32, h: u32, min: u32, s: u32, us: u32) -> DateTimeValue {
DateTimeValue {
year: y,
month: m,
day: d,
hour: h,
minute: min,
second: s,
microsecond: us,
timezone: Some(TimezoneValue {
offset_hours: 0,
offset_minutes: 0,
}),
granularity: DateGranularity::DateTime,
}
}
fn eval_bool(engine: &Engine, spec_name: &str, rule: &str, eff: &DateTimeValue) -> bool {
eval_bool_with_datas(engine, spec_name, rule, eff, HashMap::new())
}
fn eval_bool_with_datas(
engine: &Engine,
spec_name: &str,
rule: &str,
eff: &DateTimeValue,
data: HashMap<String, String>,
) -> bool {
let response = engine
.run(None, spec_name, Some(eff), data, true, None)
.unwrap();
let rr = response
.results
.values()
.find(|r| r.rule.name == rule)
.unwrap_or_else(|| panic!("rule '{}' not found", rule));
rr.boolean.expect("boolean rule result")
}
fn eval_value(
engine: &Engine,
spec_name: &str,
rule: &str,
eff: &DateTimeValue,
) -> lemma::LiteralValue {
let response = engine
.run(None, spec_name, Some(eff), HashMap::new(), true, None)
.unwrap();
response
.results
.values()
.find(|r| r.rule.name == rule)
.unwrap_or_else(|| panic!("rule '{}' not found", rule))
.explanation
.as_ref()
.expect("explanation")
.result
.value()
.unwrap()
.clone()
}
#[test]
fn now_in_past_is_always_false() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule check: now in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
assert!(!eval_bool(
&engine,
"test",
"check",
&effective(2026, 3, 7, 12, 0, 0)
));
}
#[test]
fn now_in_future_is_always_false() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule check: now in future
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
assert!(!eval_bool(
&engine,
"test",
"check",
&effective(2026, 3, 7, 12, 0, 0)
));
}
#[test]
fn now_in_past_0_days_excludes_now() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule check: now in past 0 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
assert!(!eval_bool(
&engine,
"test",
"check",
&effective(2026, 3, 7, 12, 0, 0)
));
}
#[test]
fn now_in_future_0_days_excludes_now() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule check: now in future 0 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
assert!(!eval_bool(
&engine,
"test",
"check",
&effective(2026, 3, 7, 12, 0, 0)
));
}
#[test]
fn calendar_week_iso_year_boundary_dec_31_2025() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event_date: 2025-12-31
rule check: event_date in calendar week
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2025, 12, 30, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn calendar_week_iso_week_53() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event_date: 2027-01-01
rule check: event_date in calendar week
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 12, 31, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn past_calendar_week_from_week_2_wraps_to_week_1() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event_date: 2025-12-30
rule check: event_date in past calendar week
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 1, 5, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn past_calendar_week_from_week_1_wraps_to_previous_year() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event_date: 2025-12-25
rule check: event_date in past calendar week
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2025, 12, 31, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn future_calendar_week_from_week_52_wraps_to_next_year() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event_date: 2026-01-02
rule check: event_date in future calendar week
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2025, 12, 24, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_with_date_arithmetic() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data start_date: 2026-02-01
rule check: (start_date + 30 days) in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_future_with_date_arithmetic() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data start_date: 2026-03-01
rule check: (start_date + 30 days) in future
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn now_plus_duration_produces_future_date() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule deadline: now + 30 days
rule is_future: deadline in future
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "is_future", &eff));
}
#[test]
fn now_minus_duration_produces_past_date() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule past_point: now - 30 days
rule is_past: past_point in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "is_past", &eff));
}
#[test]
fn in_past_weeks_tolerance() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-01
rule check: event in past 2 weeks
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_hours_tolerance() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T08:00:00Z
rule check: event in past 6 hours
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_minutes_tolerance() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T11:30:00Z
rule check: event in past 45 minutes
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_future_weeks_tolerance() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-15
rule check: event in future 2 weeks
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn date_only_compared_with_now_with_time() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07
rule check: event in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn date_only_at_midnight_vs_now_at_midnight() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07
rule check: event in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 0, 0, 0);
assert!(!eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn timezone_date_east_vs_utc_now() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T01:00:00+05:00
rule check: event in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 6, 21, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn timezone_date_west_vs_utc_now() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-06T20:00:00-05:00
rule check: event in future
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 0, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn calendar_year_different_timezones_same_utc_instant() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2027-01-01T00:30:00+02:00
rule check: event in calendar year
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 6, 15, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn fractional_seconds_in_datetime_literal() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T12:00:00.500000Z
data event2: 2026-03-07T12:00:00.499999Z
rule check: event > event2
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn fractional_seconds_in_now_effective() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T12:00:00.000001Z
rule check: event in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective_us(2026, 3, 7, 12, 0, 0, 2);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn fractional_seconds_same_second_different_microsecond() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-03-07T12:00:00.000001Z
rule check: event in future
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn date_sugar_result_used_in_unless() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data order_date: 2026-03-05
data amount: 100
rule discount: 0
unless order_date in past 3 days then 10
unless order_date in past 3 days and amount > 50 then 20
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
let lit = eval_value(&engine, "test", "discount", &eff);
if let ValueKind::Number(n) = &lit.value {
assert_eq!(
lemma::ValueKind::Number(n.clone())
.as_decimal_magnitude()
.unwrap(),
rust_decimal::Decimal::from(20)
);
} else {
panic!("expected Number, got {:?}", lit.value);
}
}
#[test]
fn now_arithmetic_in_rule_chain() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
rule thirty_days_ago: now - 30 days
rule sixty_days_ago: now - 60 days
rule window_size: sixty_days_ago...thirty_days_ago as seconds as number
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
let lit = eval_value(&engine, "test", "window_size", &eff);
if let ValueKind::Number(seconds) = &lit.value {
assert_eq!(
lemma::ValueKind::Number(seconds.clone())
.as_decimal_magnitude()
.unwrap(),
rust_decimal::Decimal::from(2_592_000)
);
} else {
panic!("expected Number, got {:?}", lit.value);
}
}
#[test]
fn not_in_past_is_equivalent_to_not_past() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-04-01
rule check: not (event in past)
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn not_in_future_on_past_date() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-01-01
rule check: not (event in future)
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn format_round_trip_now_keyword() {
let input = "spec test
uses lemma units\n\nrule current: now\n";
let formatted = lemma::format_source(input, lemma::SourceType::Volatile).unwrap();
assert!(formatted.contains("now"), "got: {}", formatted);
let reparsed = lemma::format_source(&formatted, lemma::SourceType::Volatile).unwrap();
assert_eq!(formatted, reparsed, "double format should be idempotent");
}
#[test]
fn very_old_date_in_past() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 1900-01-01
rule check: event in past
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn very_old_date_not_in_calendar_year() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 1900-01-01
rule check: event not in calendar year
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn calendar_month_feb_29_not_in_march() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2024-02-29
rule check: event in calendar month
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2024, 3, 7, 12, 0, 0);
assert!(!eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn past_calendar_month_feb_from_march_leap_year() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2024-02-29
rule check: event in past calendar month
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2024, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_365_days_tolerance() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2025-06-15
rule check: event in past 365 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_365_days_outside() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2024-01-01
rule check: event in past 365 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(!eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn two_dates_different_calendar_checks() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data start_date: 2026-03-05
data end_date: 2025-09-15
rule start_this_month: start_date in calendar month
rule end_last_year: end_date in past calendar year
rule both: start_this_month and end_last_year
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(eval_bool(&engine, "test", "both", &eff));
}
#[test]
fn effective_at_jan_1_midnight_calendar_year_boundary() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2025-12-31T23:59:59Z
rule check: event in past calendar year
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 1, 1, 0, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn effective_at_month_1_midnight_calendar_month_boundary() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-02-28T23:59:59Z
rule check: event in past calendar month
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 1, 0, 0, 0);
assert!(eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_past_tolerance_with_future_date() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-04-01
rule check: event in past 7 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(!eval_bool(&engine, "test", "check", &eff));
}
#[test]
fn in_future_tolerance_with_past_date() {
let mut engine = Engine::new();
let code = r#"
spec test
uses lemma units
data event: 2026-01-01
rule check: event in future 7 days
"#;
engine.load(code, lemma::SourceType::Volatile).unwrap();
let eff = effective(2026, 3, 7, 12, 0, 0);
assert!(!eval_bool(&engine, "test", "check", &eff));
}