qrush_engine/cron/
cron_parser.rs

1// src/cron/cron_parser.rs
2
3use anyhow::{anyhow, Result};
4use chrono::{DateTime, TimeZone, Utc};
5use chrono_tz::Tz;
6use cron::Schedule;
7use std::str::FromStr;
8
9pub struct CronParser;
10
11impl CronParser {
12    /// Compute next execution time for a cron expression.
13    ///
14    /// - `cron_expr`: standard 5/6-field cron expression supported by `cron` crate
15    /// - `from_utc`: "now" moment as UTC
16    /// - `timezone`: tz name like "UTC", "Asia/Kolkata"
17    ///
18    /// Returns next run time in UTC.
19    pub fn next_execution(cron_expr: &str, from_utc: DateTime<Utc>, timezone: &str) -> Result<DateTime<Utc>> {
20        let schedule = Schedule::from_str(cron_expr)
21            .map_err(|e| anyhow!("invalid cron expression '{}': {:?}", cron_expr, e))?;
22
23        let tz: Tz = timezone
24            .parse()
25            .map_err(|_| anyhow!("invalid timezone '{}'", timezone))?;
26
27        let from_tz = from_utc.with_timezone(&tz);
28
29        // IMPORTANT:
30        // `after()` is on Schedule, not on ScheduleIterator.
31        let next_tz = schedule
32            .after(&from_tz)
33            .next()
34            .ok_or_else(|| anyhow!("no upcoming cron occurrence for expression '{}'", cron_expr))?;
35
36        Ok(next_tz.with_timezone(&Utc))
37    }
38
39    /// Convenience: validate cron expr
40    pub fn validate(cron_expr: &str) -> Result<()> {
41        let _ = Schedule::from_str(cron_expr)
42            .map_err(|e| anyhow!("invalid cron expression '{}': {:?}", cron_expr, e))?;
43        Ok(())
44    }
45
46    /// Parse a tz string into chrono_tz::Tz
47    pub fn parse_tz(timezone: &str) -> Result<Tz> {
48        timezone
49            .parse::<Tz>()
50            .map_err(|_| anyhow!("invalid timezone '{}'", timezone))
51    }
52
53    /// Helper: convert UTC timestamp to tz DateTime
54    pub fn to_tz(ts_utc: DateTime<Utc>, tz: Tz) -> DateTime<Tz> {
55        tz.from_utc_datetime(&ts_utc.naive_utc())
56    }
57}