loopd: five-field UTC cron matcher
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
//! Five-field UTC crontab matcher (M5d spec, section 4.2). No cron crate.
|
||||
|
||||
use bxcore::Timestamp;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CronError {
|
||||
Fields(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CronError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CronError::Fields(schedule) => {
|
||||
write!(f, "cron schedule {schedule:?} is not five UTC fields")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CronError {}
|
||||
|
||||
/// Whether `schedule` matches `now` in UTC.
|
||||
pub fn due(schedule: &str, now: Timestamp) -> Result<bool, CronError> {
|
||||
let fields: Vec<&str> = schedule.split_whitespace().collect();
|
||||
if fields.len() != 5 {
|
||||
return Err(CronError::Fields(schedule.to_string()));
|
||||
}
|
||||
let rfc = now.to_rfc3339();
|
||||
let minute = number(&rfc, 14, 16, schedule)?;
|
||||
let hour = number(&rfc, 11, 13, schedule)?;
|
||||
let day = number(&rfc, 8, 10, schedule)?;
|
||||
let month = number(&rfc, 5, 7, schedule)?;
|
||||
let dow = {
|
||||
let days = now.unix_millis() / 1000 / 86_400;
|
||||
((days + 4) % 7) as u32
|
||||
};
|
||||
Ok(match_field(fields[0], minute, 0, 59, schedule)?
|
||||
&& match_field(fields[1], hour, 0, 23, schedule)?
|
||||
&& match_field(fields[2], day, 1, 31, schedule)?
|
||||
&& match_field(fields[3], month, 1, 12, schedule)?
|
||||
&& match_field(fields[4], dow, 0, 6, schedule)?)
|
||||
}
|
||||
|
||||
fn number(rfc: &str, start: usize, end: usize, schedule: &str) -> Result<u32, CronError> {
|
||||
let piece = rfc
|
||||
.get(start..end)
|
||||
.ok_or_else(|| CronError::Fields(schedule.to_string()))?;
|
||||
piece
|
||||
.parse()
|
||||
.map_err(|_| CronError::Fields(schedule.to_string()))
|
||||
}
|
||||
|
||||
fn match_field(
|
||||
field: &str,
|
||||
value: u32,
|
||||
min: u32,
|
||||
max: u32,
|
||||
schedule: &str,
|
||||
) -> Result<bool, CronError> {
|
||||
let err = || CronError::Fields(schedule.to_string());
|
||||
if field == "*" {
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(step) = field.strip_prefix("*/") {
|
||||
let n: u32 = step.parse().map_err(|_| err())?;
|
||||
if n == 0 {
|
||||
return Err(err());
|
||||
}
|
||||
return Ok(value.is_multiple_of(n));
|
||||
}
|
||||
if let Some((a, b)) = field.split_once('-') {
|
||||
let lo: u32 = a.parse().map_err(|_| err())?;
|
||||
let hi: u32 = b.parse().map_err(|_| err())?;
|
||||
if lo < min || hi > max || lo > hi {
|
||||
return Err(err());
|
||||
}
|
||||
return Ok(value >= lo && value <= hi);
|
||||
}
|
||||
let n: u32 = field.parse().map_err(|_| err())?;
|
||||
if n < min || n > max {
|
||||
return Err(err());
|
||||
}
|
||||
Ok(n == value)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub mod broker_port;
|
||||
pub mod channel;
|
||||
pub mod compact;
|
||||
pub mod config;
|
||||
pub mod cron;
|
||||
pub mod http;
|
||||
pub mod llama;
|
||||
pub mod memory;
|
||||
|
||||
Reference in New Issue
Block a user