Claude-skill-registry croner

Cron expression parsing and scheduling

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/croner" ~/.claude/skills/majiayu000-claude-skill-registry-croner && rm -rf "$T"
manifest: skills/data/croner/SKILL.md
source content

croner

A fully-featured, lightweight Rust library for parsing and evaluating cron patterns. Used in script-kit-gpui for scheduled script execution.

Key Types

Cron

The main struct for working with cron expressions. Parse from string and query for occurrences.

use croner::Cron;
use std::str::FromStr;
use chrono::Utc;

// Parse cron expression
let cron = Cron::from_str("0 9 * * *")?;  // Every day at 9 AM

// Access the pattern
let pattern_str = cron.pattern.to_string();

CronPattern

Accessible via

cron.pattern
, contains the parsed representation of each field.

CronError

Error type returned when parsing fails or time operations fail.

CronIterator

Iterator for traversing scheduled times forward or backward.

Usage in script-kit-gpui

Scripts can specify schedules using metadata comments:

// Cron: */5 * * * *
// Raw cron pattern - runs every 5 minutes

// Schedule: every tuesday at 2pm
// Natural language - converted to cron via english-to-cron

The

Scheduler
in
src/scheduler.rs
manages script execution:

use croner::Cron;
use std::str::FromStr;
use chrono::Utc;

// Parse and validate cron expression
pub fn parse_cron(expr: &str) -> Result<Cron> {
    Cron::from_str(expr)
        .map_err(|e| anyhow::anyhow!("Invalid cron expression '{}': {}", expr, e))
}

// Find next scheduled run time
fn find_next_occurrence(cron: &Cron, after: &DateTime<Utc>) -> Result<DateTime<Utc>> {
    cron.find_next_occurrence(after, false)  // false = exclusive (not including current time)
        .map_err(|e| anyhow::anyhow!("Failed to find next occurrence: {:?}", e))
}

Cron Syntax

Standard 5-field cron (optional 6th field for seconds):

// Standard 5-field (minute precision)
// ┌────────────── minute (0 - 59)
// │ ┌──────────── hour (0 - 23)
// │ │ ┌────────── day of month (1 - 31)
// │ │ │ ┌──────── month (1 - 12, JAN-DEC)
// │ │ │ │ ┌────── day of week (0 - 6, SUN-SAT, 7 = SUN)
// │ │ │ │ │
// * * * * *

// Optional 6-field (second precision)
// ┌──────────────── second (0 - 59)
// │ ┌────────────── minute (0 - 59)
// │ │ ┌──────────── hour (0 - 23)
// │ │ │ ┌────────── day of month (1 - 31)
// │ │ │ │ ┌──────── month (1 - 12)
// │ │ │ │ │ ┌────── day of week (0 - 6)
// │ │ │ │ │ │
// * * * * * *

Special Characters

CharMeaningExample
*
Any value
* * * * *
(every minute)
,
List of values
1,15 * * * *
(minute 1 and 15)
-
Range
0 9-17 * * *
(9am to 5pm hourly)
/
Step values
*/5 * * * *
(every 5 minutes)
?
No specific value
0 0 ? * MON
(any day of month)
L
Last
0 0 L * *
(last day of month)
W
Weekday
0 0 15W * *
(nearest weekday to 15th)
#
Nth weekday
0 0 * * 1#2
(2nd Monday)

Common Patterns

// Every minute
"* * * * *"

// Every 5 minutes
"*/5 * * * *"

// Every hour at minute 0
"0 * * * *"

// Every day at 9:00 AM
"0 9 * * *"

// Every Monday at 2:30 PM
"30 14 * * 1"

// Every weekday at 9 AM
"0 9 * * 1-5"

// First day of month at midnight
"0 0 1 * *"

// Every 15 minutes during business hours
"*/15 9-17 * * 1-5"

Next Occurrence

Finding when a cron pattern will next match:

use croner::Cron;
use chrono::Utc;
use std::str::FromStr;

let cron = Cron::from_str("0 9 * * *")?;
let now = Utc::now();

// Next occurrence after now (exclusive)
let next = cron.find_next_occurrence(&now, false)?;

// Next occurrence including now if it matches (inclusive)
let next_inclusive = cron.find_next_occurrence(&now, true)?;

// Previous occurrence before now
let previous = cron.find_previous_occurrence(&now, false)?;

// Check if current time matches pattern
let matches = cron.is_time_matching(&now)?;

Iterating Occurrences

use croner::{Cron, Direction};
use chrono::Utc;

let cron = Cron::from_str("0 * * * *")?;
let now = Utc::now();

// Get next 5 occurrences
let upcoming: Vec<_> = cron.iter_after(now)
    .take(5)
    .collect();

// Get previous 5 occurrences
let past: Vec<_> = cron.iter_before(now)
    .take(5)
    .collect();

// Iterate with direction
let iter = cron.iter_from(now, Direction::Forward);

Human-Readable Descriptions

Generate English descriptions of cron patterns:

let cron = Cron::from_str("0 12 * * MON-FRI")?;
let description = cron.describe();
// "At on minute 0, at hour 12, on Monday,Tuesday,Wednesday,Thursday,Friday."

english-to-cron

Convert natural language schedules to cron expressions (via separate crate):

use english_to_cron::str_cron_syntax;

// Basic conversions
str_cron_syntax("every minute")?         // "* * * * *"
str_cron_syntax("every hour")?           // "0 * * * *"
str_cron_syntax("every day at 9am")?     // "0 9 * * *"
str_cron_syntax("every tuesday at 2pm")? // "0 14 * * 2"
str_cron_syntax("every weekday at 8am")? // "0 8 * * 1-5"

In script-kit-gpui:

pub fn natural_to_cron(text: &str) -> Result<String> {
    english_to_cron::str_cron_syntax(text)
        .map_err(|e| anyhow::anyhow!("Failed to convert '{}' to cron: {:?}", text, e))
}

Anti-patterns

Don't forget error handling

// BAD: Panics on invalid pattern
let cron = Cron::from_str("invalid").unwrap();

// GOOD: Handle parse errors
let cron = Cron::from_str(expr)
    .map_err(|e| anyhow::anyhow!("Invalid cron: {}", e))?;

Don't use inclusive=true unless needed

// BAD: May match the current second, causing immediate double-trigger
let next = cron.find_next_occurrence(&now, true)?;

// GOOD: Exclusive search for scheduling
let next = cron.find_next_occurrence(&now, false)?;

Don't ignore timezone considerations

// BAD: Assuming UTC when user expects local time
let now = Utc::now();
let next = cron.find_next_occurrence(&now, false)?;

// GOOD: Be explicit about timezone handling
// croner uses chrono's DateTime<Tz> which is timezone-aware
// Document whether schedules are UTC or local

Don't parse inside tight loops

// BAD: Parsing every iteration
for _ in 0..100 {
    let cron = Cron::from_str("*/5 * * * *")?;
    let next = cron.find_next_occurrence(&now, false)?;
}

// GOOD: Parse once, reuse
let cron = Cron::from_str("*/5 * * * *")?;
for _ in 0..100 {
    let next = cron.find_next_occurrence(&now, false)?;
}

Don't mix up field order

// BAD: Confusing minute and hour (runs at 9:00, not 0:09)
"0 9 * * *"  // This is 9:00 AM, not 12:09 AM

// GOOD: Use comments to clarify intent
"0 9 * * *"  // minute=0, hour=9 -> 9:00 AM daily

Don't forget to validate natural language conversion

// BAD: Assuming conversion always succeeds
let cron_expr = english_to_cron::str_cron_syntax(user_input)?;

// GOOD: Validate the result and provide feedback
match english_to_cron::str_cron_syntax(user_input) {
    Ok(expr) => {
        // Also validate the generated expression
        Cron::from_str(&expr)?;
        Ok(expr)
    }
    Err(e) => Err(anyhow::anyhow!(
        "Could not understand schedule '{}': {:?}", user_input, e
    ))
}

Dependencies

In

Cargo.toml
:

croner = "3.0"             # Parse and validate cron expressions
english-to-cron = "0.1"    # Convert natural language schedules to cron
chrono = "0.4"             # Date/time handling (croner's timezone support)

References