1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
use std::str::FromStr;
use bevy_ecs::prelude::Local;
use chrono::DateTime;
use cron::Schedule;
/// bevy_cronjob is a simple helper to run cronjobs (at repeated schedule) in Bevy.
/// # Usage
///
/// ``` rust,no_run
/// use std::time::Duration;
/// use bevy::{ MinimalPlugins};
/// use bevy::app::{App, PluginGroup, ScheduleRunnerPlugin, Update};
/// use bevy::log::{info, LogPlugin};
///
/// use bevy_ecs::prelude::{IntoSystemConfigs};
/// use bevy_cronjob::schedule_passed;
///
/// fn main() {
/// App::new()
/// .add_plugins(
/// MinimalPlugins.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(
/// 1.0 / 60.0,
/// ))),
/// )
/// .add_plugins(LogPlugin::default())
/// .add_systems(Update, print_per_5_sec.run_if(schedule_passed("0/5 * * * * *")))
/// .add_systems(Update, print_per_min.run_if(schedule_passed("0 * * * * *")))
/// .add_systems(Update, print_per_hour.run_if(schedule_passed("0 0 * * * *")))
/// .run()
/// }
///
/// fn print_per_5_sec() {
/// info!("print every 5 sec")
/// }
///
/// fn print_per_min() {
/// info!("print every minute")
/// }
/// fn print_per_hour() {
/// info!("print every hour")
/// }
/// ```
///
/// ## Expression
///
/// the scheduling expression is base on [cron](https://github.com/zslayton/cron)
///
/// | sec | min | hour | day of month | month | day of week | year |
/// |------|------|------|--------------|-------|-------------|-----------|
/// | * | * | * | * | * | * | * |
/// | 0-59 | 0-59 | 0-23 | 1-23 | 1-12 | 1-7 | 1970-2100 |
///
/// Time is specified in UTC. Note that the year may be omitted.
///
/// Comma separated values such as `1,2,3` are allowed. For example, a schedule of `0,15,30,45 * * * * *`' would execute on every 15 seconds.
///
/// Ranges can be specified with a dash. For example `1-5 * * * * *`' would execute on every second for the first 5 seconds of a minute.
/// run every 5 sec
pub const EVERY_5_SEC: &str = "0/5 * * * * * *";
/// run every 10 sec
pub const EVERY_10_SEC: &str = "0/10 * * * * * *";
/// run every 30 sec
pub const EVERY_30_SEC: &str = "0/30 * * * * * *";
/// run every minute
pub const EVERY_MIN: &str = "0 * * * * * *";
/// run every 5 minutes
pub const EVERY_5_MIN: &str = "0 */5 * * * * *";
/// run every 10 minutes
pub const EVERY_10_MIN: &str = "0 */10 * * * * *";
/// run every 30 minutes
pub const EVERY_30_MIN: &str = "0 */30 * * * * *";
/// run every hour
pub const EVERY_HOUR: &str = "0 0 * * * * *";
/// run every day
pub const EVERY_DAY: &str = "0 0 0 * * * *";
/// Creates a closure that checks if the cron expression has passed
/// # expression format:
/// Note that the year may be omitted.
/// | sec | min | hour | day of month | month | day of week | year |
/// |------|------|------|--------------|-------|-------------|-----------|
/// | * | * | * | * | * | * | * |
/// | 0-59 | 0-59 | 0-23 | 1-23 | 1-12 | 1-7 | 1970-2100 |
///
/// # example:
/// | expression | description|
/// |------|------|
/// |0/5 * * * * * | every 5 sec|
/// |0 * * * * * | every minute |
/// |0 5,10 * * * * | every hour on 5 and 10 min|
/// |0 0 1 * * * | every day on 1:00:00|
pub fn schedule_passed(
expression: &str,
) -> impl FnMut(Local<Option<DateTime<chrono::Utc>>>) -> bool {
let schedule = Schedule::from_str(expression).expect("Failed to parse cron expression");
move |mut local_schedule: Local<Option<DateTime<chrono::Utc>>>| {
if let Some(datetime) = schedule.upcoming(chrono::Utc).next() {
let now = chrono::Utc::now();
match *local_schedule {
Some(local) => {
if now > local {
*local_schedule = Some(datetime);
return true;
}
}
None => *local_schedule = Some(datetime),
}
}
false
}
}