A detour to Rust: Creating a Rust flavor of Croner
by Hexagon, 4 minutes read rust croner cron scheduling programming
So, there I was, diving into Deno's new feature, Deno.cron, when something caught my eye. It was missing a few things – support for time zones, second granularity, and some extended cron-syntax I like. It got me thinking: could I fill these gaps? Despite not having dabbled in Rust before, I had a fair bit of fun creating the croner library for JavaScript and TypeScript, so why not give it a shot in Rust?
Rolling up my sleeves - entirely new to Rust, but curious, I wasn't just looking
to patch up what I found lacking in Deno.cron; I wanted to build something that
could improve on the existing libraries. That's how the Rust version of croner
started taking shape – not as a direct copy of its JavaScript sibling, but as a
efficient Rust library that does a bit more than just parse and evaluate basic
cron patterns.
This new croner
, just as it JavaScript sibling, sticks to the good old
POSIX/Vixie-cron standards but with a twist – it brings in some handy extras
like L
, #
, and W
. Plus, of course timezone support and an option for
second granularity, aiming to make it the cron-library Rust lacks.
While exploring the current options, including saffron
which is the crate ued
by Deno today, I noticed a total lack of options. As an example, both saffron
and cron
have chosen to go with an alternative numbering of weekdays
introduced and embraced by the Java commutity through Quartz, using 1 for SUN, 2
for MON etc. This does not follow the POSIX-standard used by your regular
"system cron", which uses 0 for SUN etc. As a result of that, croner aims to
adhere to standards, but introduces options that allow users to tailor how cron
patterns patterns are interpreted:
with_seconds_optional
: Brings an extra layer of precision to scheduling. Users can include seconds in their cron patterns if they need to, but it's not a must.with_seconds_required
: Ensures that every cron pattern explicitly defines the seconds, making scheduling more usable for real time applications.with_alternative_weekdays
: While croner use POSIX-numbering (0=SUN,1=MON,...) by default, this switches the weekday mode to Quartz-style (1=SUN,2=MON) etc., commonly used in Java-based scheduling systems. While I'm not fond of this, several Rust applications already seem to have adapted the Quartz-numbering.with_dom_and_dow
: Allows for usingAND
to combine Day of Month (DOM) and Day of Week (DOW) conditions in a cron expressions. The end-user should be able to use this when needed, to create pattern that matches both day of month, and day of week - like when new years is on a friday.
To give a complete picture of the diffreneces of each crate, I'll just snip a comparison table i made for the GitHub README, and edit out the parts supported by both croner and the pre-existing alternatives;
Feature | Croner | Cron | Saffron |
---|---|---|---|
Time Zone-support | X | X | |
L - Last day of month |
X | X | |
L - Last occurrence of weekday |
X | ||
# - Nth occurrence of weekday |
X | X | |
W - Closest weekday |
X | X | |
"Standards"-compliant weekdays (1 is monday) | X | ||
Five part patterns (minute granularity) | X | X | |
Six part patterns (second granularity) | X | X | |
Aliases (@hourly etc.) |
X | X | |
DOM-and-DOW option | X |
Getting Started with Croner
Designed to be a (almost) drop-in-replacement for Saffron, Croner is simple to use out of the box. The only third party depenceny is chrono.
To give croner a try. Just add it to your Cargo.toml
, and try out one of the
examples below:
[dependencies]
croner = "2.0.1" # Adjust the version as necessary
chrono = "0.4.31"
# Optionally
# chrono_tz = "0.8.4"
Usage Examples:
-
Matching Current Time and Finding Next Occurrence:
use croner::Cron; use chrono::Local; fn main() { let cron = Cron::new("18 * * * 5").parse().expect("Couldn't parse cron string"); let time = Local::now(); let matches = cron.is_time_matching(&time).unwrap(); let next = cron.find_next_occurrence(&time, false).unwrap(); println!("Pattern matches at {}", next); }
-
Working with Different Time Zones:
use croner::Cron; use chrono::{Local, TimeZone}; use chrono_tz::Tz; fn main() { let cron = Cron::new("18 * * * 5").parse().expect("Couldn't parse cron string"); let est_timezone: Tz = "America/New_York".parse().expect("Invalid timezone"); let time_est = Local::now().with_timezone(&est_timezone); let next_est = cron.find_next_occurrence(&time_est, false).unwrap(); println!("Next match in EST: {}", next_est); }
-
Calculating Specific Occurrences (e.g., New Year's Eve on a Friday):
use croner::Cron; use chrono::Local; fn main() { let cron = Cron::new("0 0 0 31 12 FRI") .with_seconds_optional() .with_dom_and_dow() .parse() .expect("Couldn't parse cron string"); let time = Local::now(); for time in cron.iter_from(time).take(5) { println!("{}", time); } }
Join the discussion ...
As croner is nearing completion, I'm trying to get the discussion going by creating a few issues at the Deno repository. If you're interested in the topic, join in on the discussion:
-
Bug: Deno.cron Day-of-Week Mismatch (Issue #21555): This issue points out a discrepancy in how numeric weekdays are handled in Deno.cron. Addressing this could enhance the accuracy and usability of cron patterns.
-
Feature Request: Enhancing Deno.cron for Second Granularity (Issue #21561): This feature request focuses on incorporating (or at least allowing) seconds into cron patterns.
-
Feature Request: Timezone Support for Deno.cron (Issue #21562): Time zone support is crucial for global applications.
... Or Join the Development
I'd also like to encourage you to check out the repository at github.com/hexagon/croner-rust. Try it out, find any yet undiscovered oddities, and/or review the code and get back to me thorugh an Issue. As I mentioned before, this is my first contact with Rust and I'm still learning, so I appreciate any kind of feedback.
The Future: A Full-Blown Threaded Scheduler for Rust
Besides hoping that the Deno crew adopts croner
in Deno, I've begun working on
a stand-alone threaded scheduler for Rust, utilizing croner
at its core. This
project aims to bring more advanced and standards-compliant scheduling to Rust
applications. See
github.com/hexagon/croner-scheduler-rust
or the croner-scheduler
crate.