chronos_parser_rs/
cron_evaluator.rs1use 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}