Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ path = "src/lib.rs"

[dependencies]
chrono = "0.4.38"
derive_builder = "0.20.2"
serde = { version = "1.0", optional = true }
strum = { version = "0.27.1", features = ["derive"] }

[dev-dependencies]
chrono-tz = "0.10.0"
Expand Down
132 changes: 77 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ This is the Rust flavor of the popular JavaScript/TypeScript cron parser

Croner combines the features of cron and saffron, while following the POSIX/Vixie "standards" for the relevant parts. See this table:

Feature | Croner | Cron | Saffron |
---------------------|-------------|-----------|---------|
Time Zones | X | X | |
Ranges (15-25)| X | X | X |
Ranges with stepping (15-25/2)| X | X | X | X |
`L` - Last day of month | X | | X |
`5#L` - Last occurrence of weekday | X | X | |
`5L` - Last occurrence of weekday | X | ? | 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 | |
Weekday/Month text representations | X | X | X |
Aliases (`@hourly` etc.) | X | X | |
chrono `DateTime` compatibility | X | X | X |
DOM-and-DOW option | X | | |
| Feature | Croner | Cron | Saffron |
|----------------------|-------------|-----------|---------|
| Time Zones | X | X | |
| Ranges (15-25)| X | X | X |
| Ranges with stepping (15-25/2)| X | X | X |
| `L` - Last day of month | X | | X |
| `5#L` - Last occurrence of weekday | X | X | |
| `5L` - Last occurrence of weekday | X | ? | 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 | |
| Weekday/Month text representations | X | X | X |
| Aliases (`@hourly` etc.) | X | X | |
| chrono `DateTime` compatibility | X | X | X |
| DOM-and-DOW option | X | | |

> **Note**
> Tests carried out at 2023-12-02 using `cron@0.12.0` and `saffron@.0.1.0`
Expand Down Expand Up @@ -76,8 +76,7 @@ use chrono::Local;
fn main() {

// Parse cron expression
let cron_all = Cron::new("18 * * * 5")
.parse()
let cron_all = Cron::from_str("18 * * * 5")
.expect("Couldn't parse cron string");

// Compare cron pattern with current local time
Expand Down Expand Up @@ -105,8 +104,7 @@ use chrono_tz::Tz;

fn main() {
// Parse cron expression
let cron = Cron::new("18 * * * 5")
.parse()
let cron = Cron::from_str("18 * * * 5")
.expect("Couldn't parse cron string");

// Choose a different time zone, for example America/New_York
Expand All @@ -126,27 +124,28 @@ fn main() {
}
```

This example demonstrates how to calculate the next 5 occurrences of New Year's Eve that fall on a Friday. We'll use a cron expression to match every Friday (`FRI`) in December (`12`) and use the `with_dom_and_dow` method to ensure both day of month and day of week conditions are met.
This example demonstrates how to calculate the next 5 occurrences of New Year's Eve that fall on a Friday. We'll use a cron expression to match every Friday (`FRI`) in December (`12`) and configure `dom_and_dow` to ensure both day-of-month and day-of-week conditions are met (see [configuration](#configuration) for more details).

```rust
use croner::Cron;
use chrono::Local;
use croner::parser::CronParser;

fn main() {
// Parse cron expression for Fridays in December
let cron = Cron::new("0 0 0 31 12 FRI")
// Include seconds in pattern
.with_seconds_optional()
// Ensure both day of month and day of week conditions are met
.with_dom_and_dow()
.parse()
.expect("Couldn't parse cron string");
let cron = CronParser::builder()
// Include seconds in pattern
.seconds(croner::parser::Seconds::Optional)
// Ensure both day of month and day of week conditions are met
.dom_and_dow(true)
.build()
.parse("0 0 0 31 12 FRI")
.expect("Couldn't parse cron string");

let time = Local::now();

println!("Finding the next 5 New Year's Eves on a Friday:");
for time in cron.iter_from(time).take(5) {
println!("{}", time);
println!("{time}");
}
}
```
Expand All @@ -169,8 +168,8 @@ a few additions and changes as outlined below:
```

- Croner expressions have the following additional modifiers:
- _?_: In the Rust version of croner, a questionmark in the day-of-month or
day-of-week field behaves just as `*`. This allow for legacy cron patterns
- _?_: In the Rust version of croner, a questionmark in the day-of-month or
day-of-week field behaves just as `*`. This 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
Expand Down Expand Up @@ -203,9 +202,9 @@ a few additions and changes as outlined below:
> 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.

> **Note:** The `W` feature is constrained within the given month. The search for
> **Note:** The `W` feature is constrained within the given month. The search for
> the closest weekday will not cross into a previous or subsequent month. For
> example, if the 1st of the month is a Saturday, 1W will trigger on Monday
> example, if the 1st of the month is a Saturday, 1W will trigger on Monday
> the 3rd, not the last Friday of the previous month.

It is also possible to use the following "nicknames" as pattern.
Expand All @@ -216,58 +215,81 @@ It is also possible to use the following "nicknames" as pattern.
| \@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 * * *". |
| \@daily | Run once a day, ie. "0 0 * * *". |
| \@hourly | Run once an hour, ie. "0 * * * *". |

### Configuration

Croner offers several configuration methods to change how patterns are interpreted:
Croner uses `CronParser` to parse the cron expression. Invoking
`Cron::from_str("pattern")` is equivalent to
`CronParser::new().parse("pattern")`. You can customise the parser by creating a
parser builder using `CronParser::builder`.

#### 1. `with_seconds_optional()`
#### 1. Making seconds optional

This method enables the inclusion of seconds in the cron pattern, but it's not mandatory. By using this method, you can create cron patterns that either include or omit the seconds field. This offers greater flexibility, allowing for more precise scheduling without imposing the strict requirement of defining seconds in every pattern.
This option enables the inclusion of seconds in the cron pattern, but it's not mandatory. By using this option, you can create cron patterns that either include or omit the seconds field. This offers greater flexibility, allowing for more precise scheduling without imposing the strict requirement of defining seconds in every pattern.

**Example Usage**:

```rust
let cron = Cron::new("*/10 * * * * *") // Every 10 seconds
.with_seconds_optional()
.parse()
use croner::parser::{CronParser, Seconds};

// Configure the parser to allow seconds.
let parser = CronParser::builder().seconds(Seconds::Optional).build();

let cron = parser
.parse("*/10 * * * * *") // Every 10 seconds
.expect("Invalid cron pattern");
```

#### 2. `with_seconds_required()`
#### 2. Making seconds optional required

In contrast to `with_seconds_optional()`, the `with_seconds_required()` method requires the seconds field in every cron pattern. This enforces a high level of precision in task scheduling, ensuring that every pattern explicitly specifies the second at which the task should run.
In contrast to `Seconds::Optional`, the `Seconds::Required` variant requires the seconds field in every cron pattern. This enforces a high level of precision in task scheduling, ensuring that every pattern explicitly specifies the second at which the task should run.

**Example Usage**:

```rust
let cron = Cron::new("5 */2 * * * *") // At 5 seconds past every 2 minutes
.with_seconds_required()
.parse()
use croner::parser::{CronParser, Seconds};

// Configure the parser to require seconds.
let parser = CronParser::builder().seconds(Seconds::Required).build();

let cron = parser
.parse("5 */2 * * * *") // At 5 seconds past every 2 minutes
.expect("Invalid cron pattern");
```

#### 3. `with_dom_and_dow()`
#### 3. `dom_and_dow`

This method enables the combination of Day of Month (DOM) and Day of Week (DOW) conditions in your cron expressions. It's particularly useful for creating schedules that require specificity in terms of both the day of the month and the day of the week, such as running a task when the first of the month is a Monday, or christmas day is on a friday.

**Example Usage**:

```rust
let cron = Cron::new("0 0 25 * FRI") // When christmas day is on a friday
.with_dom_and_dow()
.parse()
use croner::parser::CronParser;

// Configure the parser to enable DOM and DOW.
let parser = CronParser::builder().dom_and_dow(true).build();

let cron = parser
.parse("0 0 25 * FRI") // When christmas day is on a friday
.expect("Invalid cron pattern");
```

#### 4. `with_alternative_weekdays()` (Quartz mode)
#### 4. `alternative_weekdays` (Quartz mode)

This configuration method switches the weekday mode from the POSIX standard to the Quartz-style, commonly used in Java-based scheduling systems. It's useful for those who are accustomed to Quartz's way of specifying weekdays or for ensuring compatibility with existing Quartz-based schedules.

**Example Usage**:

```rust
let cron = Cron::new("0 0 12 * * 6") // Every Friday (denoted with 6 in Quartz mode) at noon
.with_alternative_weekdays()
.parse()
use croner::parser::CronParser;

// Configure the parser to use Quartz-style weekday mode.
let parser = CronParser::builder().alternative_weekdays(true).build();

let cron = parser
.parse("0 0 12 * * 6") // Every Friday (denoted with 6 in Quartz mode) at noon
.expect("Invalid cron pattern");
```

Expand Down
9 changes: 5 additions & 4 deletions benches/croner_bench.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use chrono::Local;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use croner::Cron;
use croner::{parser::CronParser, Cron};

fn parse_take_100(_n: u64) {
let cron: Cron = Cron::new("15 15 15 L 3 *")
.with_seconds_optional()
.parse()
let cron: Cron = CronParser::builder()
.seconds(croner::parser::Seconds::Optional)
.build()
.parse("15 15 15 L 3 *")
.expect("Couldn't parse cron string");
let time = Local::now();
for _time in cron.clone().iter_after(time).take(100) {}
Expand Down
9 changes: 5 additions & 4 deletions examples/iter_demo.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use chrono::Utc;
use croner::Cron;
use croner::parser::CronParser;

fn main() {
// Parse cron expression
let cron = Cron::new("* * * * * *")
.with_seconds_optional()
.parse()
let cron = CronParser::builder()
.seconds(croner::parser::Seconds::Optional)
.build()
.parse("* * * * * *")
.expect("Couldn't parse cron string");

// Compare to UTC time now
Expand Down
9 changes: 5 additions & 4 deletions examples/simple_demo.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use chrono::Local;
use croner::Cron;
use croner::parser::CronParser;

fn main() {
// Example: Parse cron expression
let cron = Cron::new("0 18 * * * FRI")
.with_seconds_required()
.parse()
let cron = CronParser::builder()
.seconds(croner::parser::Seconds::Required)
.build()
.parse("0 18 * * * FRI")
.expect("Couldn't parse cron string");

// Example: Compare cron pattern with current local time
Expand Down
6 changes: 3 additions & 3 deletions examples/timezone_demo.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::str::FromStr as _;

use chrono::Utc;
use chrono_tz::Tz;
use croner::Cron;

fn main() {
// Parse cron expression
let cron = Cron::new("18 * * * 5")
.parse()
.expect("Couldn't parse cron string");
let cron = Cron::from_str("18 * * * 5").expect("Couldn't parse cron string");

// Find the next occurrence in Europe/Stockholm
let now_stockholm = Utc::now().with_timezone(&Tz::Europe__Stockholm);
Expand Down
7 changes: 6 additions & 1 deletion src/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ where
/// * `start_time` - The `DateTime` to start iterating from.
/// * `inclusive` - Whether the `start_time` should be included in the results if it matches.
/// * `direction` - The direction to iterate in (Forward or Backward).
pub fn new(cron: Cron, start_time: DateTime<Tz>, inclusive: bool, direction: Direction) -> Self {
pub fn new(
cron: Cron,
start_time: DateTime<Tz>,
inclusive: bool,
direction: Direction,
) -> Self {
CronIterator {
cron,
current_time: start_time,
Expand Down
Loading