chronos_parser_rs/
cron_evaluator.rs

1use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike};
2
3use crate::Expr;
4
5pub struct CronEvaluator<'a, Tz: TimeZone> {
6  instant: &'a DateTime<Tz>,
7}
8
9#[derive(Debug, Clone)]
10pub struct Environment {
11  now: u8,
12  max: u8,
13}
14
15impl Environment {
16  pub fn new(now: u8, max: u8) -> Self {
17    Self { now, max }
18  }
19}
20
21fn get_days_from_month(year: i32, month: u32) -> Option<i64> {
22  NaiveDate::from_ymd_opt(
23    match month {
24      12 => year + 1,
25      _ => year,
26    },
27    match month {
28      12 => 1,
29      _ => month + 1,
30    },
31    1,
32  )
33  .map(|d| {
34    d.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
35      .num_days()
36  })
37}
38
39impl<'a, Tz: TimeZone> CronEvaluator<'a, Tz> {
40  pub fn new(instant: &'a DateTime<Tz>) -> Self {
41    Self { instant }
42  }
43
44  pub fn eval(&self, ast: &Expr) -> bool {
45    match ast {
46      Expr::CronExpr {
47        box mins,
48        box hours,
49        box months,
50        box days,
51        box day_of_weeks,
52      } => {
53        let last_day =
54          get_days_from_month(self.instant.date_naive().year(), self.instant.date_naive().month()).unwrap();
55        let fmins = self.visit0(&Environment::new(self.instant.time().minute() as u8, 59), mins);
56        let fhours = self.visit0(&Environment::new(self.instant.time().hour() as u8, 23), hours);
57        let fdays = self.visit0(
58          &Environment::new(self.instant.date_naive().day() as u8, last_day as u8),
59          days,
60        );
61        let fmonths = self.visit0(&Environment::new(self.instant.date_naive().month() as u8, 12), months);
62        let fday_of_weeks = self.visit0(&Environment::new(self.instant.time().minute() as u8, 7), day_of_weeks);
63        fmins && fhours && fdays && fmonths && fday_of_weeks
64      }
65      _ => false,
66    }
67  }
68
69  fn visit1(&self, env: &Environment, ast: &Expr) -> bool {
70    match ast {
71      Expr::AnyValueExpr => true,
72      Expr::LastValueExpr if env.now == env.max => true,
73      Expr::ValueExpr(n) if env.now == *n => true,
74      Expr::ListExpr(list) => list.iter().any(|e| self.visit0(env, e)),
75      Expr::RangeExpr {
76        from: box Expr::ValueExpr(start),
77        to: box Expr::ValueExpr(end),
78        per_option,
79      } => match per_option {
80        box Expr::NoOp if *start <= env.now && env.now <= *end => true,
81        box Expr::ValueExpr(per) => (*start as usize..=*end as usize)
82          .step_by(*per as usize)
83          .into_iter()
84          .any(|e| e == env.now as usize),
85        _ => false,
86      },
87      Expr::PerExpr {
88        digit: box Expr::AnyValueExpr,
89        option: box Expr::ValueExpr(per),
90      } => (0usize..=(env.max as usize))
91        .step_by(*per as usize)
92        .into_iter()
93        .any(|e| e == env.now as usize),
94      _ => false,
95    }
96  }
97
98  fn visit0(&self, env: &Environment, ast: &Expr) -> bool {
99    self.visit1(env, ast)
100  }
101}
102
103#[cfg(test)]
104mod tests {
105  use chrono::{TimeZone, Utc};
106
107  use crate::cron_evaluator::CronEvaluator;
108  use crate::Expr;
109
110  #[test]
111  fn test_anytime() {
112    let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 1, 1, 1).unwrap();
113    let cron_evaluator = CronEvaluator::new(&date_time);
114    let expr = Expr::CronExpr {
115      mins: Box::from(Expr::AnyValueExpr),
116      hours: Box::from(Expr::AnyValueExpr),
117      days: Box::from(Expr::AnyValueExpr),
118      months: Box::from(Expr::AnyValueExpr),
119      day_of_weeks: Box::from(Expr::AnyValueExpr),
120    };
121    let result = cron_evaluator.eval(&expr);
122    assert!(result)
123  }
124
125  #[test]
126  fn test_point_time() {
127    let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 1, 1, 1).unwrap();
128    let cron_evaluator = CronEvaluator::new(&date_time);
129    let expr = Expr::CronExpr {
130      mins: Box::from(Expr::ValueExpr(1)),
131      hours: Box::from(Expr::ValueExpr(1)),
132      days: Box::from(Expr::ValueExpr(1)),
133      months: Box::from(Expr::ValueExpr(1)),
134      day_of_weeks: Box::from(Expr::AnyValueExpr),
135    };
136    let result = cron_evaluator.eval(&expr);
137    assert!(result)
138  }
139}