A dependency-free cron expression parser and schedule matcher for MoonBit.
///|
test {
let schedule = match @moon_cron.parse("*/15 9-17 * * MON-FRI") {
Ok(value) => value
Err(_) => fail("valid cron expression expected")
}
let monday = @moon_cron.UtcTime::new(30, 9, 6, 7, 1)
assert_true(schedule.matches(monday))
assert_eq(
schedule.describe(),
"every 15 minutes past hours 9 through 17 on Monday through Friday",
)
let now = @moon_cron.UtcDateTime::new(2026, 7, 6, 9, 30).unwrap()
assert_true(
schedule.next_after(now) ==
Some(@moon_cron.UtcDateTime::new(2026, 7, 6, 9, 45).unwrap()),
)
}///|
test {
let document = @moon_cron.parse_crontab(
(
#|# Operations
#|SHELL=/bin/sh
#|0 9 * * MON-FRI open-office
#|*/15 9-17 * * MON-FRI collect-metrics
),
).unwrap()
assert_eq(document.entry_count(), 2)
assert_eq(document.variable("SHELL"), Some("/bin/sh"))
let at = @moon_cron.parse_utc_datetime("2026-07-27T09:00Z").unwrap()
assert_eq(document.due_at(at).length(), 2)
}///|
test {
let cron = @moon_cron.parse("0 9-17 * * MON-FRI").unwrap()
let policy = @moon_cron.SchedulePolicy::new(cron)
.unwrap()
.add_blackout(
@moon_cron.ScheduleBlackout::new(
@moon_cron.DateTimeRange::new(
@moon_cron.parse_utc_datetime("2026-07-27T12:00Z").unwrap(),
@moon_cron.parse_utc_datetime("2026-07-27T14:00Z").unwrap(),
).unwrap(),
reason="maintenance",
),
)
let noon = @moon_cron.parse_utc_datetime("2026-07-27T12:00Z").unwrap()
assert_eq(policy.decision_at(noon), @moon_cron.Excluded("maintenance"))
let book = @moon_cron.ScheduleBook::new()
book.add(@moon_cron.NamedSchedule::new("office", cron).unwrap()).unwrap()
let day = @moon_cron.DateTimeRange::new(
@moon_cron.parse_utc_datetime("2026-07-27T00:00Z").unwrap(),
@moon_cron.parse_utc_datetime("2026-07-27T23:59Z").unwrap(),
).unwrap()
assert_eq(book.report(day).total_events, 9)
}moon run cmd/mainmoon check --target all --deny-warn
moon build --target all --deny-warn
moon test --target all --deny-warn
moon fmt --check
moon info
moon packagepub struct CollisionStatistics {
left_name : String
right_name : String
count : Int
first : UtcDateTime?
last : UtcDateTime?
truncated : Bool
} derive(Eq, Debug)fn Cron::collisions_with(self : Cron, other : Cron, range : DateTimeRange, limit? : Int) -> Array[ScheduleCollision]fn Cron::daily_counts(self : Cron, range : DateTimeRange, occurrence_limit? : Int) -> Array[DailyOccurrenceCount]fn Cron::gap_summary(self : Cron, range : DateTimeRange, sample_limit? : Int) -> OccurrenceGapSummary?fn Cron::occurrence_page(self : Cron, cursor : UtcDateTime, end : UtcDateTime, limit : Int) -> Result[OccurrencePage, CronError]pub enum CronError {
WrongFieldCount(Int)
InvalidNumber(String)
UnknownName(String)
ValueOutOfRange(String, Int, Int)
InvalidRange(String)
UnsupportedSyntax(String)
InvalidDate(String)
InvalidField(String)
InvalidWindow(String)
DuplicateSchedule(String)
MissingSchedule(String)
InvalidCrontabLine(Int, String)
} derive(Eq, Debug)fn CrontabEntry::new(schedule_text : String, command : String, line_number? : Int) -> Result[CrontabEntry, CronError]fn CrontabEntry::with_command(self : CrontabEntry, command : String) -> Result[CrontabEntry, CronError]fn CrontabEntry::with_schedule(self : CrontabEntry, schedule_text : String) -> Result[CrontabEntry, CronError]pub(all) enum CrontabLine {
Blank
Comment(String)
Variable(CrontabVariable)
Entry(CrontabEntry)
} derive(Eq, Debug)fn CrontabVariable::new(name : String, value : String, line_number? : Int) -> Result[CrontabVariable, CronError]pub struct NamedCollision {
left_name : String
right_name : String
at : UtcDateTime
} derive(Eq, Debug)fn NamedSchedule::new(name : String, cron : Cron, enabled? : Bool) -> Result[NamedSchedule, CronError]pub struct OccurrencePage {
values : Array[UtcDateTime]
next_cursor : UtcDateTime?
} derive(Eq, Debug)fn ScheduleBook::collisions(self : ScheduleBook, range : DateTimeRange, per_pair_limit? : Int) -> Array[NamedCollision]fn ScheduleBook::event_batches(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int) -> Array[EventBatch]fn ScheduleBook::events(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int) -> Array[ScheduledEvent]fn ScheduleBook::next_events_after(self : ScheduleBook, cursor : UtcDateTime) -> Array[ScheduledEvent]fn ScheduleBook::replace_cron(self : ScheduleBook, name : String, cron : Cron) -> Result[Unit, CronError]fn ScheduleBook::report(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int, per_pair_limit? : Int) -> ScheduleReportfn ScheduleBook::set_enabled(self : ScheduleBook, name : String, enabled : Bool) -> Result[Unit, CronError]pub struct SchedulePolicy {
cron : Cron
enabled : Bool
blackouts : Array[ScheduleBlackout]
inclusions : Array[ScheduleInclusion]
} derive(Eq, Debug)fn SchedulePolicy::add_blackout(self : SchedulePolicy, blackout : ScheduleBlackout) -> SchedulePolicyfn SchedulePolicy::add_inclusion(self : SchedulePolicy, inclusion : ScheduleInclusion) -> SchedulePolicyfn SchedulePolicy::blackouts_in(self : SchedulePolicy, range : DateTimeRange) -> Array[ScheduleBlackout]fn SchedulePolicy::count_occurrences(self : SchedulePolicy, range : DateTimeRange, limit? : Int) -> OccurrenceCountfn SchedulePolicy::inclusions_in(self : SchedulePolicy, range : DateTimeRange) -> Array[ScheduleInclusion]fn SchedulePolicy::occurrences(self : SchedulePolicy, range : DateTimeRange, limit? : Int) -> Array[UtcDateTime]fn SchedulePolicy::with_cron(self : SchedulePolicy, cron : Cron) -> Result[SchedulePolicy, CronError]pub struct ScheduleReport {
range : DateTimeRange
schedules : Array[ScheduleStatistics]
collisions : Array[CollisionStatistics]
daily_load : Array[DailyLoad]
total_events : Int
peak : PeakMinute?
truncated : Bool
} derive(Eq, Debug)fn ScheduleReport::collision_for(self : ScheduleReport, left_name : String, right_name : String) -> CollisionStatistics?pub struct ScheduleStatistics {
name : String
enabled : Bool
occurrence_count : Int
truncated : Bool
first : UtcDateTime?
last : UtcDateTime?
active_days : Int
minimum_gap_minutes : Int?
maximum_gap_minutes : Int?
average_gap_minutes : Int?
frequency : FrequencyClass
hourly : Array[HourlyBucket]
weekdays : Array[WeekdayBucket]
} derive(Eq, Debug)fn UtcDateTime::new(year : Int, month : Int, day : Int, hour : Int, minute : Int) -> Result[UtcDateTime, CronError]fn analyze_collision(left : NamedSchedule, right : NamedSchedule, range : DateTimeRange, limit? : Int) -> CollisionStatisticsfn analyze_schedule(schedule : NamedSchedule, range : DateTimeRange, limit? : Int) -> ScheduleStatisticsfn days_in_month(year : Int, month : Int) -> IntA dependency-free cron expression parser and schedule matcher for MoonBit.