Croner
Croner is a fully featured, lightweight, efficient Rust library for parsing and evaluating cron patterns. Designed with simplicity and performance in mind, it provides Rust developers with a tool to schedule tasks efficiently, following the familiar cron syntax.
This is the Rust flavor of the popular JavaScript/TypeScript cron parser croner.
Features
- Parse and evaluate cron expressions to calculate upcoming execution times.
- Schedule tasks in separate threads based on cron patterns, enabling concurrent task execution.
- Supports extended Vixie-cron patterns with additional specifiers such as
L
for the last day and weekday of the month,#
for the nth weekday of the month andW
for closest weekday to a day of month. - Evaulate cron expressions across different time zones.
- Compatible with
chrono
and (optionally)chrono-tz
. - Robust error handling.
- Control execution flow with the ability to pause, resume, or stop scheduled tasks.
- Operates in-memory without the need for persistent storage or configuration files.
- Highly optimized method of finding future/past matches.
- No dependencies except
chrono
.
Getting Started
Prerequisites
Ensure you have Rust installed on your machine. If not, you can get it from the official Rust website.
Installation
Add croner
to your Cargo.toml
dependencies:
Please note that croner for Rust is work in progress, and not production ready
[dependencies]
croner = "1.0.0" # Adjust the version as necessary
Usage
Here's a quick example to get you started with matching current time, and finding the next occurrence. is_time_matching
takes a chrono
DateTime
:
use croner::Cron;
use chrono::Local;
fn main() {
// Parse cron expression
let cron_all: Cron = "0 18 * * * 5".parse().expect("Couldn't parse cron string");
// Compare cron pattern with current local time
let time = Local::now();
let matches_all = cron_all.is_time_matching(&time).unwrap();
// Get next match
let next = cron_all.find_next_occurrence(&time, false).unwrap();
// Output results
println!("Time is: {}", time);
println!("Pattern \"{}\" does {} time {}", cron_all.pattern.to_string(), if matches_all { "match" } else { "not match" }, time );
println!("Pattern \"{}\" will match next time at {}", cron_all.pattern.to_string(), next);
}
To match against a non local timezone, croner supports zoned chrono DateTime's DateTime<Tz>
. To use a named time zone, you can utilize the chrono-tz
crate.
use croner::Cron;
use chrono::Local;
use chrono_tz::Tz;
fn main() {
// Parse cron expression
let cron = Cron::parse("0 18 * * * 5").expect("Couldn't parse cron string");
// Choose a different time zone, for example America/New_York
let est_timezone: Tz = "America/New_York".parse().expect("Invalid timezone");
// Find the next occurrence in EST
let time_est = Local::now().with_timezone(&est_timezone);
let next_est = cron.find_next_occurrence(&time_est, false).unwrap();
// Output results for EST
println!("EST time is: {}", time_est);
println!(
"Pattern \"{}\" will match next time at (EST): {}",
cron.pattern.to_string(),
next_est
);
}
Here is a basic example to schedule a task:
use croner::Cron;
use croner::scheduler::CronScheduler;
use chrono::Utc;
use std::thread;
fn main() {
let cron: Cron = "0/5 * * * * *".parse().expect("Invalid cron expression");
let scheduler = CronScheduler::new(cron, Utc);
scheduler.start(|| {
println!("Task executed at {:?}", Utc::now());
});
// The task can be paused, resumed, or stopped as needed
// scheduler.pause();
// scheduler.resume();
// scheduler.stop();
// For demonstration purposes, let's run for some time then stop
thread::sleep(std::time::Duration::from_secs(20));
}
Pattern
The expressions used by Croner are very similar to those of Vixie Cron, but with a few additions and changes as outlined below:
// ┌──────────────── (optional) second (0 - 59)
// │ ┌────────────── minute (0 - 59)
// │ │ ┌──────────── hour (0 - 23)
// │ │ │ ┌────────── day of month (1 - 31)
// │ │ │ │ ┌──────── month (1 - 12, JAN-DEC)
// │ │ │ │ │ ┌────── day of week (0 - 6, SUN-Mon)
// │ │ │ │ │ │ (0 to 6 are Sunday to Saturday; 7 is Sunday, the same as 0)
// │ │ │ │ │ │
// * * * * * *
- Croner expressions have the following additional modifiers:
- ?: In the Rust version of croner, a questionmark behaves just as *, to allow for legacy cron patterns to be used.
- L: The letter 'L' can be used in the day of the month field to indicate the last day of the month. When used in the day of the week field in conjunction with the # character, it denotes the last specific weekday of the month. For example,
5#L
represents the last Friday of the month. - #: The # character specifies the "nth" occurrence of a particular day within a month. For example, supplying
5#2
in the day of week field signifies the second Friday of the month. This can be combined with ranges and supports day names. For instance, MON-FRI#2 would match the Monday through Friday of the second week of the month. - W: The character 'W' is used to specify the closest weekday to a given day in the day of the month field. For example, 15W will match the closest weekday to the 15th of the month. If the specified day falls on a weekend (Saturday or Sunday), the pattern will match the closest weekday before or after that date. For instance, if the 15th is a Saturday, 15W will match the 14th (Friday), and if the 15th is a Sunday, it will match the 16th (Monday).
Field | Required | Allowed values | Allowed special characters | Remarks |
---|---|---|---|---|
Seconds | Optional | 0-59 | * , - / ? | |
Minutes | Yes | 0-59 | * , - / ? | |
Hours | Yes | 0-23 | * , - / ? | |
Day of Month | Yes | 1-31 | * , - / ? L W | |
Month | Yes | 1-12 or JAN-DEC | * , - / ? | |
Day of Week | Yes | 0-7 or SUN-MON | * , - / ? # L | 0 to 6 are Sunday to Saturday 7 is Sunday, the same as 0 # is used to specify nth occurrence of a weekday |
Note Weekday and month names are case-insensitive. Both
MON
andmon
work. When usingL
in the Day of Week field, it affects all specified weekdays. For example,5-6#L
means the last Friday and Saturday in the month." The # character can be used to specify the "nth" weekday of the month. For example, 5#2 represents the second Friday of the month.
It is also possible to use the following "nicknames" as pattern.
Nickname | Description |
---|---|
@yearly | Run once a year, ie. "0 0 1 1 *". |
@annually | Run once a year, ie. "0 0 1 1 *". |
@monthly | Run once a month, ie. "0 0 1 * *". |
@weekly | Run once a week, ie. "0 0 * * 0". |
@daily | Run once a day, ie. "0 0 * * *". |
@hourly | Run once an hour, ie. "0 * * * *". |
Documentation
For detailed usage and API documentation, visit Croner on docs.rs.
Development
To start developing in the Croner project:
- Clone the repository.
- Navigate into the project directory.
- Build the project using
cargo build
. - Run tests with
cargo test
. - Run demo with
cargo run --example pattern_demo
Contributing
We welcome contributions! Please feel free to submit a pull request or open an issue.
License
This project is licensed under the MIT License - see the LICENSE.md file for details.
Disclaimer
Please note that Croner is currently in its early stages of development. As such, the API is subject to change in future releases, adhering to semantic versioning principles. We recommend keeping this in mind when integrating Croner into your projects.
Contact
If you have any questions or feedback, please open an issue in the repository and we'll get back to you as soon as possible.