moon_cron

A dependency-free cron expression parser and schedule matcher for MoonBit.

cron
schedule
scheduler
time
parser
moon add p1nbored/moon_cron@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
4 hours ago
Downloads
2
README

#MoonCron

MoonCron is a dependency-free, cross-target cron-expression core for MoonBit. It fills the gap between an application's clock and its work queue: parse a portable cron expression once, then use a typed schedule to decide whether a UTC minute should trigger work, compute upcoming occurrences, or explain the schedule to a human.

#Current scope

  • Five-field cron: minute, hour, day of month, month, weekday.
  • *, exact values, ranges (9-17), steps (*/15, 9-17/2, 5/15) and comma lists (0,15,30,45).
  • Month and weekday names (JAN, january, MON-FRI), case-insensitive, and weekday 7 as a second spelling of Sunday.
  • Scheduling macros: @hourly, @daily, @midnight, @weekly, @monthly, @yearly and @annually.
  • Standard cron day-of-month / weekday OR semantics when both are restricted, with */n steps anchored at each field's lowest value.
  • A validated UtcDateTime calendar type with leap-year handling and weekday computation, signed date arithmetic, closed time ranges, next_after / previous_before, bounded occurrence queries and paging.
  • Human-readable schedule descriptions via describe.
  • Preset builders: every_minute, hourly, weekday_hourly, daily_at, weekly_on and monthly_on.
  • Programmatic field algebra for validation, normalization, union, intersection, difference and overlap prechecks.
  • Source-aware crontab documents with comments, environment assignments, macros, command lookup, due-command queries and stable rendering.
  • Named schedule registries with enablement, merged events, collision detection and bounded audits.
  • Operational policies with maintenance blackouts and one-off inclusions.
  • Frequency, hourly/weekday distribution, peak concurrency, collision and health reports suitable for command-line or CI artifacts.
  • No dependencies or backend-specific APIs; currently checked on wasm, wasm-gc, JavaScript and native targets.

#Use it

///|
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()),
)
}

Parse and query a complete crontab document:

///|
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)
}

Apply a maintenance blackout and produce an operational report:

///|
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)
}

Run the included example with:

moon run cmd/main

#Delivered scale

The package now contains more than 4,000 non-test MoonBit source lines. The workload is enforced in CI using scripts/count-production-lines.ps1, which excludes tests, generated interfaces, examples and build output. The implementation is split by capability rather than padded into a single file.

#Non-goals

MoonCron is not a job runner, daemon, timezone database or distributed task queue. Those layers can depend on this small deterministic scheduling core.

#Development

moon check --target all --deny-warn moon build --target all --deny-warn moon test --target all --deny-warn moon fmt --check moon info moon package

The GitHub Actions workflow runs the same quality gates, verifies generated interfaces remain clean, runs the example and enforces the production-code floor. The project is original MoonBit code and is licensed under Apache-2.0.

#
CollisionStatistics

pub struct CollisionStatistics {
left_name : String
right_name : String
count : Int
first : UtcDateTime?
last : UtcDateTime?
truncated : Bool
} derive(Eq,
Debug
)

Pairwise collision statistics.

#
Cron

pub struct Cron {
minute : Field
hour : Field
day_of_month : Field
month : Field
weekday : Field
} derive(Eq,
Debug
)

A five-field cron schedule: minute, hour, day-of-month, month and weekday.

#
Cron::collides_with

fn Cron::collides_with(self : Cron, other : Cron, range : DateTimeRange) -> Bool

True when both schedules fire at least once at the same minute.

#
Cron::collisions_with

fn Cron::collisions_with(self : Cron, other : Cron, range : DateTimeRange, limit? : Int) -> Array[ScheduleCollision]

Find simultaneous occurrences in a bounded range.

#
Cron::count_occurrences

fn Cron::count_occurrences(self : Cron, range : DateTimeRange, limit? : Int) -> OccurrenceCount

#
Cron::daily_counts

fn Cron::daily_counts(self : Cron, range : DateTimeRange, occurrence_limit? : Int) -> Array[DailyOccurrenceCount]

#
Cron::describe

fn Cron::describe(self : Cron) -> String

Render the schedule as an English sentence fragment, for example "every 15 minutes past hours 9 through 17 on Monday through Friday". When both day fields are restricted the wording mirrors cron's OR rule.

#
Cron::field_summary

fn Cron::field_summary(self : Cron) -> CronFieldSummary

#
Cron::first_occurrence

fn Cron::first_occurrence(self : Cron, range : DateTimeRange) -> UtcDateTime?

#
Cron::from_fields

fn Cron::from_fields(minute : Field, hour : Field, day_of_month : Field, month : Field, weekday : Field) -> Result[Cron, CronError]

Construct and validate a schedule from programmatic fields.

#
Cron::gap_summary

fn Cron::gap_summary(self : Cron, range : DateTimeRange, sample_limit? : Int) -> OccurrenceGapSummary?

Analyze up to sample_limit occurrences in a range.

#
Cron::has_occurrence

fn Cron::has_occurrence(self : Cron, range : DateTimeRange) -> Bool

#
Cron::last_occurrence

fn Cron::last_occurrence(self : Cron, range : DateTimeRange) -> UtcDateTime?

#
Cron::matches

fn Cron::matches(self : Cron, time : UtcTime) -> Bool

Check whether a UTC wall-clock minute satisfies this schedule. When both day-of-month and weekday are restricted, standard cron's OR rule is used.

#
Cron::matches_at

fn Cron::matches_at(self : Cron, at : UtcDateTime) -> Bool

Check a schedule directly against a validated date-time.

#
Cron::maximum_daily_runs

fn Cron::maximum_daily_runs(self : Cron) -> Int

Coarse upper bound on runs in a 24-hour day, ignoring date fields.

#
Cron::may_overlap

fn Cron::may_overlap(self : Cron, other : Cron) -> Bool

True when two schedules can share at least one minute/hour/month and calendar selector. This is a fast preflight; use occurrence queries for an exact answer inside a bounded date range.

#
Cron::next_after

fn Cron::next_after(self : Cron, from : UtcDateTime) -> UtcDateTime?

The first minute strictly after from that satisfies the schedule, or None when no occurrence exists within the ten-year search horizon (for example a schedule pinned to day 30 of February).

#
Cron::next_occurrences

fn Cron::next_occurrences(self : Cron, from : UtcDateTime, limit : Int) -> Array[UtcDateTime]

Up to limit occurrences strictly after from, in chronological order. Fewer are returned when the schedule stops producing matches inside the search horizon.

#
Cron::next_on_or_after

fn Cron::next_on_or_after(self : Cron, from : UtcDateTime) -> UtcDateTime?

First occurrence at or after from.

#
Cron::normalized

fn Cron::normalized(self : Cron) -> Cron

Normalize every field while preserving matching semantics.

#
Cron::occurrence_page

fn Cron::occurrence_page(self : Cron, cursor : UtcDateTime, end : UtcDateTime, limit : Int) -> Result[OccurrencePage, CronError]

Return a page strictly after cursor, capped by both limit and end time.

#
Cron::occurrences

fn Cron::occurrences(self : Cron, query : OccurrenceQuery) -> Array[UtcDateTime]

Materialize occurrences in the inclusive range.

#
Cron::previous_before

fn Cron::previous_before(self : Cron, from : UtcDateTime) -> UtcDateTime?

Last minute strictly before from that satisfies the schedule.

#
Cron::previous_on_or_before

fn Cron::previous_on_or_before(self : Cron, from : UtcDateTime) -> UtcDateTime?

Last occurrence at or before from.

#
Cron::to_expression

fn Cron::to_expression(self : Cron) -> String

#
Cron::validate

fn Cron::validate(self : Cron) -> Result[Unit, CronError]

Validate a programmatically constructed schedule.

#
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
)

#
CronFieldSummary

pub struct CronFieldSummary {
minute_values : Int
hour_values : Int
day_of_month_values : Int
month_values : Int
weekday_values : Int
unrestricted_fields : Int
} derive(Eq,
Debug
)

Structural facts useful for diagnostics and user interfaces.

#
CrontabDocument

pub struct CrontabDocument {
lines : Array[CrontabLine]
} derive(Eq,
Debug
)

A parsed crontab document. Blank lines and comments are retained so tools can inspect and render documents without throwing away useful context.

#
CrontabDocument::all_lines

#
CrontabDocument::append_blank

fn CrontabDocument::append_blank(self : CrontabDocument) -> Unit

#
CrontabDocument::append_comment

fn CrontabDocument::append_comment(self : CrontabDocument, comment : String) -> Unit

#
CrontabDocument::append_entry

fn CrontabDocument::append_entry(self : CrontabDocument, entry : CrontabEntry) -> Unit

#
CrontabDocument::append_variable

fn CrontabDocument::append_variable(self : CrontabDocument, variable : CrontabVariable) -> Unit

#
CrontabDocument::comment_count

fn CrontabDocument::comment_count(self : CrontabDocument) -> Int

#
CrontabDocument::comments

fn CrontabDocument::comments(self : CrontabDocument) -> Array[String]

#
CrontabDocument::compact

Remove all comments and blank lines while preserving executable semantics.

#
CrontabDocument::due_at

Commands due at an exact UTC minute, in document order.

#
CrontabDocument::entries

#
CrontabDocument::entry_count

fn CrontabDocument::entry_count(self : CrontabDocument) -> Int

#
CrontabDocument::find_commands

fn CrontabDocument::find_commands(self : CrontabDocument, fragment : String) -> Array[CrontabEntry]

Entries whose command contains a literal text fragment.

#
CrontabDocument::from_lines

fn CrontabDocument::from_lines(lines : Array[CrontabLine]) -> CrontabDocument

#
CrontabDocument::line_count

fn CrontabDocument::line_count(self : CrontabDocument) -> Int

#
CrontabDocument::new

#
CrontabDocument::to_schedule_book

fn CrontabDocument::to_schedule_book(self : CrontabDocument) -> ScheduleBook

Convert document entries to a named schedule registry. Line numbers make otherwise duplicate commands independently addressable.

#
CrontabDocument::to_text

fn CrontabDocument::to_text(self : CrontabDocument) -> String

Render a normalized document. Cron macros are retained as entered, while whitespace between schedule fields and commands is canonicalized.

#
CrontabDocument::variable

fn CrontabDocument::variable(self : CrontabDocument, name : String) -> String?

Return the last value assigned to name, matching crontab override rules.

#
CrontabDocument::variable_count

fn CrontabDocument::variable_count(self : CrontabDocument) -> Int

#
CrontabDocument::variables

#
CrontabEntry

pub struct CrontabEntry {
schedule_text : String
cron : Cron
command : String
line_number : Int
} derive(Eq,
Debug
)

A schedulable command from a crontab document.

#
CrontabEntry::matches_at

fn CrontabEntry::matches_at(self : CrontabEntry, at : UtcDateTime) -> Bool

True when this command is due at the supplied UTC minute.

#
CrontabEntry::new

fn CrontabEntry::new(schedule_text : String, command : String, line_number? : Int) -> Result[CrontabEntry, CronError]

#
CrontabEntry::with_command

fn CrontabEntry::with_command(self : CrontabEntry, command : String) -> Result[CrontabEntry, CronError]

Replace a command while preserving its schedule and source line.

#
CrontabEntry::with_schedule

fn CrontabEntry::with_schedule(self : CrontabEntry, schedule_text : String) -> Result[CrontabEntry, CronError]

Replace a schedule while preserving the command and source line.

#
CrontabLine

pub(all) enum CrontabLine {
Blank
Comment(String)
Variable(CrontabVariable)
Entry(CrontabEntry)
} derive(Eq,
Debug
)

A source-preserving crontab line.

#
CrontabVariable

pub struct CrontabVariable {
name : String
value : String
line_number : Int
} derive(Eq,
Debug
)

An environment assignment declared in a crontab document.

#
CrontabVariable::new

fn CrontabVariable::new(name : String, value : String, line_number? : Int) -> Result[CrontabVariable, CronError]

Construct a validated crontab environment assignment.

#
DailyLoad

pub struct DailyLoad {
date : UtcDate
event_count : Int
active_minutes : Int
peak_concurrency : Int
} derive(Eq,
Debug
)

Aggregate event load for one UTC calendar date.

#
DailyOccurrenceCount

pub struct DailyOccurrenceCount {
date : UtcDate
count : Int
} derive(Eq,
Debug
)

Partition occurrences by UTC calendar date.

#
DateTimeRange

pub struct DateTimeRange {
start : UtcDateTime
end : UtcDateTime
} derive(Eq,
Debug
)

A closed minute interval.

#
DateTimeRange::chunks

fn DateTimeRange::chunks(self : DateTimeRange, minutes : Int) -> Array[DateTimeRange]

Split the range into closed chunks containing at most minutes minutes.

#
DateTimeRange::contains

fn DateTimeRange::contains(self : DateTimeRange, value : UtcDateTime) -> Bool

#
DateTimeRange::intersection

fn DateTimeRange::intersection(self : DateTimeRange, other : DateTimeRange) -> DateTimeRange?

#
DateTimeRange::minute_count

fn DateTimeRange::minute_count(self : DateTimeRange) -> Int

Inclusive minute count in the range.

#
DateTimeRange::new

fn DateTimeRange::new(start : UtcDateTime, end : UtcDateTime) -> Result[DateTimeRange, CronError]

Construct a non-empty closed interval.

#
DateTimeRange::overlaps

fn DateTimeRange::overlaps(self : DateTimeRange, other : DateTimeRange) -> Bool

#
EventBatch

pub struct EventBatch {
at : UtcDateTime
schedule_names : Array[String]
} derive(Eq,
Debug
)

Batch of events sharing a minute.

#
Field

pub enum Field {
Any
Exact(Int)
Every(Int)
Range(Int, Int)
RangeEvery(Int, Int, Int)
List(Array[Field])
} derive(Eq,
Debug
)

One field of a cron schedule. Every counts from the lowest value the field allows, so */2 in the day-of-month field means days 1, 3, 5 and so on, matching standard cron behaviour.

#
Field::any

fn Field::any() -> Field

#
Field::cardinality

fn Field::cardinality(self : Field, domain : FieldDomain) -> Int

#
Field::difference

fn Field::difference(self : Field, other : Field, domain : FieldDomain) -> Field

Values accepted by self but rejected by other.

#
Field::equivalent_to

fn Field::equivalent_to(self : Field, other : Field, domain : FieldDomain) -> Bool

#
Field::every

fn Field::every(step : Int) -> Field

#
Field::exact

fn Field::exact(value : Int) -> Field

Programmatic exact-value field. Use Cron::from_fields to validate it against its eventual position.

#
Field::first

fn Field::first(self : Field, domain : FieldDomain) -> Int?

#
Field::intersection

fn Field::intersection(self : Field, other : Field, domain : FieldDomain) -> Field

Semantic intersection, independent of the original expression spelling.

#
Field::is_empty_in

fn Field::is_empty_in(self : Field, domain : FieldDomain) -> Bool

#
Field::is_full_in

fn Field::is_full_in(self : Field, domain : FieldDomain) -> Bool

#
Field::is_subset_of

fn Field::is_subset_of(self : Field, other : Field, domain : FieldDomain) -> Bool

#
Field::last

fn Field::last(self : Field, domain : FieldDomain) -> Int?

#
Field::list

fn Field::list(items : Array[Field]) -> Field

#
Field::matches

fn Field::matches(self : Field, value : Int, lower? : Int) -> Bool

Check whether a value satisfies this field. lower is the smallest value the field position allows; Every steps count from it, so pass lower=1 for day-of-month and month fields.

#
Field::next_at_or_after

fn Field::next_at_or_after(self : Field, value : Int, domain : FieldDomain) -> Int?

First accepted value greater than or equal to value.

#
Field::normalized

fn Field::normalized(self : Field, domain : FieldDomain) -> Field

A compact arithmetic-run representation of a semantic field.

#
Field::overlaps

fn Field::overlaps(self : Field, other : Field, domain : FieldDomain) -> Bool

#
Field::previous_at_or_before

fn Field::previous_at_or_before(self : Field, value : Int, domain : FieldDomain) -> Int?

Last accepted value less than or equal to value.

#
Field::range

fn Field::range(start : Int, end : Int) -> Field

#
Field::range_every

fn Field::range_every(start : Int, end : Int, step : Int) -> Field

#
Field::to_expression

fn Field::to_expression(self : Field) -> String

Render a field in the portable expression subset accepted by parse.

#
Field::union

fn Field::union(self : Field, other : Field, domain : FieldDomain) -> Field

Semantic union, returned in ascending canonical order.

#
Field::values

fn Field::values(self : Field, domain : FieldDomain) -> Array[Int]

Sorted unique values accepted inside the supplied domain.

#
FieldDomain

pub(all) enum FieldDomain {
MinuteDomain
HourDomain
DayOfMonthDomain
MonthDomain
WeekdayDomain
} derive(Eq,
Debug
)

The semantic domain in which a cron field is evaluated.

#
FieldDomain::bounds

fn FieldDomain::bounds(self : FieldDomain) -> (Int, Int)

#
FieldDomain::name

fn FieldDomain::name(self : FieldDomain) -> String

#
FrequencyClass

pub(all) enum FrequencyClass {
Dormant
Sparse
Moderate
Frequent
Intense
} derive(Eq,
Debug
)

Coarse operational frequency classification.

#
FrequencyClass::label

fn FrequencyClass::label(self : FrequencyClass) -> String

#
HourlyBucket

pub struct HourlyBucket {
hour : Int
count : Int
} derive(Eq,
Debug
)

Number of occurrences assigned to one UTC hour of day.

#
NamedCollision

pub struct NamedCollision {
left_name : String
right_name : String
at : UtcDateTime
} derive(Eq,
Debug
)

#
NamedSchedule

pub struct NamedSchedule {
name : String
cron : Cron
enabled : Bool
} derive(Eq,
Debug
)

A named, independently enabled schedule.

#
NamedSchedule::new

fn NamedSchedule::new(name : String, cron : Cron, enabled? : Bool) -> Result[NamedSchedule, CronError]

#
NamedSchedule::with_cron

fn NamedSchedule::with_cron(self : NamedSchedule, cron : Cron) -> Result[NamedSchedule, CronError]

#
NamedSchedule::with_enabled

fn NamedSchedule::with_enabled(self : NamedSchedule, enabled : Bool) -> NamedSchedule

#
OccurrenceCount

pub struct OccurrenceCount {
count : Int
truncated : Bool
} derive(Eq,
Debug
)

Count occurrences up to limit. The boolean is true when the actual count may be larger because the limit was reached.

#
OccurrenceGapSummary

pub struct OccurrenceGapSummary {
samples : Int
minimum_minutes : Int
maximum_minutes : Int
average_minutes : Int
} derive(Eq,
Debug
)

Summary of minute gaps between sampled occurrences.

#
OccurrencePage

pub struct OccurrencePage {
values : Array[UtcDateTime]
next_cursor : UtcDateTime?
} derive(Eq,
Debug
)

A cursor page for APIs that should not expose unbounded arrays.

#
OccurrenceQuery

pub struct OccurrenceQuery {
range : DateTimeRange
limit : Int
} derive(Eq,
Debug
)

Bounded query options for materializing occurrences.

#
OccurrenceQuery::new

fn OccurrenceQuery::new(range : DateTimeRange, limit : Int) -> Result[OccurrenceQuery, CronError]

#
PeakMinute

pub struct PeakMinute {
at : UtcDateTime
schedule_names : Array[String]
} derive(Eq,
Debug
)

One minute carrying the maximum number of simultaneous events.

#
PolicyAudit

pub struct PolicyAudit {
effective_occurrences : Int
base_occurrences : Int
suppressed_occurrences : Int
included_occurrences : Int
truncated : Bool
} derive(Eq,
Debug
)

Summarize how exceptions affect a bounded operational window.

#
PolicyDecision

pub(all) enum PolicyDecision {
Included(String)
Scheduled
Excluded(String)
Disabled
NotScheduled
} derive(Eq,
Debug
)

Explanation of why a policy does or does not fire at a minute.

#
ReportHealth

pub(all) enum ReportHealth {
Healthy
NeedsAttention
Critical
} derive(Eq,
Debug
)

Reviewer-facing health classification derived from bounded evidence.

#
ReportHealth::label

fn ReportHealth::label(self : ReportHealth) -> String

#
ScheduleAudit

pub struct ScheduleAudit {
total : Int
enabled : Int
disabled_names : Array[String]
dormant_names : Array[String]
collision_pairs : Int
} derive(Eq,
Debug
)

#
ScheduleBlackout

pub struct ScheduleBlackout {
range : DateTimeRange
reason : String
} derive(Eq,
Debug
)

A closed interval in which ordinary cron occurrences are suppressed.

#
ScheduleBlackout::contains

fn ScheduleBlackout::contains(self : ScheduleBlackout, at : UtcDateTime) -> Bool

#
ScheduleBlackout::new

fn ScheduleBlackout::new(range : DateTimeRange, reason? : String) -> ScheduleBlackout

#
ScheduleBlackout::overlaps

fn ScheduleBlackout::overlaps(self : ScheduleBlackout, range : DateTimeRange) -> Bool

#
ScheduleBook

pub struct ScheduleBook {
entries : Array[NamedSchedule]
} derive(
Debug
)

Mutable registry for application schedules. The contained array is private to callers, so all name-uniqueness rules stay centralized here.

#
ScheduleBook::add

fn ScheduleBook::add(self : ScheduleBook, entry : NamedSchedule) -> Result[Unit, CronError]

Add a unique name.

#
ScheduleBook::all

#
ScheduleBook::audit

Audit enabled schedules inside a representative window.

#
ScheduleBook::collisions

fn ScheduleBook::collisions(self : ScheduleBook, range : DateTimeRange, per_pair_limit? : Int) -> Array[NamedCollision]

Pairwise collisions among enabled schedules.

#
ScheduleBook::contains

fn ScheduleBook::contains(self : ScheduleBook, name : String) -> Bool

#
ScheduleBook::due_at

Schedules due at this exact minute, in registry order.

#
ScheduleBook::enabled_length

fn ScheduleBook::enabled_length(self : ScheduleBook) -> Int

#
ScheduleBook::enabled_only

fn ScheduleBook::enabled_only(self : ScheduleBook) -> ScheduleBook

Copy only enabled entries into a new independent registry.

#
ScheduleBook::event_batches

fn ScheduleBook::event_batches(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int) -> Array[EventBatch]

#
ScheduleBook::events

fn ScheduleBook::events(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int) -> Array[ScheduledEvent]

Merge enabled schedules into a chronological event stream.

#
ScheduleBook::from_array

fn ScheduleBook::from_array(entries : Array[NamedSchedule]) -> Result[ScheduleBook, CronError]

#
ScheduleBook::get

fn ScheduleBook::get(self : ScheduleBook, name : String) -> NamedSchedule?

#
ScheduleBook::length

fn ScheduleBook::length(self : ScheduleBook) -> Int

#
ScheduleBook::names

fn ScheduleBook::names(self : ScheduleBook) -> Array[String]

#
ScheduleBook::new

#
ScheduleBook::next_events_after

fn ScheduleBook::next_events_after(self : ScheduleBook, cursor : UtcDateTime) -> Array[ScheduledEvent]

Next event from each enabled schedule after the cursor.

#
ScheduleBook::remove

fn ScheduleBook::remove(self : ScheduleBook, name : String) -> Result[NamedSchedule, CronError]

#
ScheduleBook::replace_cron

fn ScheduleBook::replace_cron(self : ScheduleBook, name : String, cron : Cron) -> Result[Unit, CronError]

#
ScheduleBook::report

fn ScheduleBook::report(self : ScheduleBook, range : DateTimeRange, per_schedule_limit? : Int, per_pair_limit? : Int) -> ScheduleReport

#
ScheduleBook::set_enabled

fn ScheduleBook::set_enabled(self : ScheduleBook, name : String, enabled : Bool) -> Result[Unit, CronError]

#
ScheduleBook::to_text

fn ScheduleBook::to_text(self : ScheduleBook) -> String

Human-readable inventory with one schedule per line.

#
ScheduleBook::upsert

fn ScheduleBook::upsert(self : ScheduleBook, entry : NamedSchedule) -> Unit

Insert a new entry or replace the existing entry with the same name.

#
ScheduleCollision

pub struct ScheduleCollision {
at : UtcDateTime
} derive(Eq,
Debug
)

One minute at which two schedules fire together.

#
ScheduleInclusion

pub struct ScheduleInclusion {
at : UtcDateTime
reason : String
} derive(Eq,
Debug
)

A one-off occurrence that is independent of the cron expression.

#
ScheduleInclusion::new

fn ScheduleInclusion::new(at : UtcDateTime, reason? : String) -> ScheduleInclusion

#
SchedulePolicy

pub struct SchedulePolicy {
cron : Cron
enabled : Bool
blackouts : Array[ScheduleBlackout]
inclusions : Array[ScheduleInclusion]
} derive(Eq,
Debug
)

Cron plus operational exceptions. Explicit inclusions have the highest priority, allowing a controlled one-off run during a blackout or while the recurring schedule is disabled.

#
SchedulePolicy::add_blackout

fn SchedulePolicy::add_blackout(self : SchedulePolicy, blackout : ScheduleBlackout) -> SchedulePolicy

Add a blackout unless an identical range and reason already exists.

#
SchedulePolicy::add_inclusion

fn SchedulePolicy::add_inclusion(self : SchedulePolicy, inclusion : ScheduleInclusion) -> SchedulePolicy

Add or replace a one-off inclusion at the same minute.

#
SchedulePolicy::all_blackouts

fn SchedulePolicy::all_blackouts(self : SchedulePolicy) -> Array[ScheduleBlackout]

#
SchedulePolicy::all_inclusions

fn SchedulePolicy::all_inclusions(self : SchedulePolicy) -> Array[ScheduleInclusion]

#
SchedulePolicy::audit

fn SchedulePolicy::audit(self : SchedulePolicy, range : DateTimeRange, limit? : Int) -> PolicyAudit

#
SchedulePolicy::blackout_at

fn SchedulePolicy::blackout_at(self : SchedulePolicy, at : UtcDateTime) -> ScheduleBlackout?

#
SchedulePolicy::blackout_count

fn SchedulePolicy::blackout_count(self : SchedulePolicy) -> Int

#
SchedulePolicy::blackouts_in

fn SchedulePolicy::blackouts_in(self : SchedulePolicy, range : DateTimeRange) -> Array[ScheduleBlackout]

Blackouts intersecting a query window.

#
SchedulePolicy::clear_exceptions

fn SchedulePolicy::clear_exceptions(self : SchedulePolicy) -> SchedulePolicy

Copy the policy without operational exceptions.

#
SchedulePolicy::count_occurrences

fn SchedulePolicy::count_occurrences(self : SchedulePolicy, range : DateTimeRange, limit? : Int) -> OccurrenceCount

#
SchedulePolicy::decision_at

fn SchedulePolicy::decision_at(self : SchedulePolicy, at : UtcDateTime) -> PolicyDecision

#
SchedulePolicy::inclusion_at

fn SchedulePolicy::inclusion_at(self : SchedulePolicy, at : UtcDateTime) -> ScheduleInclusion?

#
SchedulePolicy::inclusion_count

fn SchedulePolicy::inclusion_count(self : SchedulePolicy) -> Int

#
SchedulePolicy::inclusions_in

fn SchedulePolicy::inclusions_in(self : SchedulePolicy, range : DateTimeRange) -> Array[ScheduleInclusion]

Inclusions contained by a query window.

#
SchedulePolicy::matches_at

fn SchedulePolicy::matches_at(self : SchedulePolicy, at : UtcDateTime) -> Bool

#
SchedulePolicy::new

fn SchedulePolicy::new(cron : Cron, enabled? : Bool) -> Result[SchedulePolicy, CronError]

#
SchedulePolicy::next_after

fn SchedulePolicy::next_after(self : SchedulePolicy, from : UtcDateTime) -> UtcDateTime?

#
SchedulePolicy::next_on_or_after

fn SchedulePolicy::next_on_or_after(self : SchedulePolicy, from : UtcDateTime) -> UtcDateTime?

First effective occurrence at or after from.

#
SchedulePolicy::occurrences

fn SchedulePolicy::occurrences(self : SchedulePolicy, range : DateTimeRange, limit? : Int) -> Array[UtcDateTime]

Materialize effective occurrences in an inclusive range.

#
SchedulePolicy::previous_before

fn SchedulePolicy::previous_before(self : SchedulePolicy, from : UtcDateTime) -> UtcDateTime?

#
SchedulePolicy::previous_on_or_before

fn SchedulePolicy::previous_on_or_before(self : SchedulePolicy, from : UtcDateTime) -> UtcDateTime?

Last effective occurrence at or before from.

#
SchedulePolicy::remove_blackout

fn SchedulePolicy::remove_blackout(self : SchedulePolicy, range : DateTimeRange) -> SchedulePolicy

Remove every blackout whose interval equals range.

#
SchedulePolicy::remove_inclusion

fn SchedulePolicy::remove_inclusion(self : SchedulePolicy, at : UtcDateTime) -> SchedulePolicy

#
SchedulePolicy::with_cron

fn SchedulePolicy::with_cron(self : SchedulePolicy, cron : Cron) -> Result[SchedulePolicy, CronError]

#
SchedulePolicy::with_enabled

fn SchedulePolicy::with_enabled(self : SchedulePolicy, enabled : Bool) -> SchedulePolicy

#
ScheduleReport

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
)

Aggregate operational report for a schedule registry.

#
ScheduleReport::active_day_count

fn ScheduleReport::active_day_count(self : ScheduleReport) -> Int

#
ScheduleReport::average_events_per_active_day

fn ScheduleReport::average_events_per_active_day(self : ScheduleReport) -> Int

#
ScheduleReport::busiest_day

fn ScheduleReport::busiest_day(self : ScheduleReport) -> DailyLoad?

#
ScheduleReport::colliding_names

fn ScheduleReport::colliding_names(self : ScheduleReport) -> Array[String]

#
ScheduleReport::collision_for

fn ScheduleReport::collision_for(self : ScheduleReport, left_name : String, right_name : String) -> CollisionStatistics?

#
ScheduleReport::collision_pair_count

fn ScheduleReport::collision_pair_count(self : ScheduleReport) -> Int

#
ScheduleReport::dormant_names

fn ScheduleReport::dormant_names(self : ScheduleReport) -> Array[String]

#
ScheduleReport::health

#
ScheduleReport::intense_names

fn ScheduleReport::intense_names(self : ScheduleReport) -> Array[String]

#
ScheduleReport::load_on

fn ScheduleReport::load_on(self : ScheduleReport, date : UtcDate) -> DailyLoad?

#
ScheduleReport::peak_concurrency

fn ScheduleReport::peak_concurrency(self : ScheduleReport) -> Int

#
ScheduleReport::recommendations

fn ScheduleReport::recommendations(self : ScheduleReport) -> Array[String]

Deterministic action hints based only on evidence contained in the report.

#
ScheduleReport::schedule_count

fn ScheduleReport::schedule_count(self : ScheduleReport) -> Int

#
ScheduleReport::statistics_for

fn ScheduleReport::statistics_for(self : ScheduleReport, name : String) -> ScheduleStatistics?

#
ScheduleReport::to_text

fn ScheduleReport::to_text(self : ScheduleReport) -> String

Stable text suitable for CI artifacts and command-line diagnostics.

#
ScheduleStatistics

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
)

Bounded statistics for one named schedule.

#
ScheduleStatistics::busiest_hour

#
ScheduleStatistics::busiest_weekday

fn ScheduleStatistics::busiest_weekday(self : ScheduleStatistics) -> WeekdayBucket?

#
ScheduleStatistics::hour_count

fn ScheduleStatistics::hour_count(self : ScheduleStatistics, hour : Int) -> Int

#
ScheduleStatistics::weekday_count

fn ScheduleStatistics::weekday_count(self : ScheduleStatistics, weekday : Int) -> Int

#
ScheduledEvent

pub struct ScheduledEvent {
schedule_name : String
at : UtcDateTime
} derive(Eq,
Debug
)

#
UtcDate

pub struct UtcDate {
year : Int
month : Int
day : Int
} derive(Compare, Eq,
Debug
)

A calendar-only value in the proleptic Gregorian calendar.

#
UtcDate::add_days

fn UtcDate::add_days(self : UtcDate, amount : Int) -> UtcDate?

Add signed calendar days, returning None outside years 1 through 9999.

#
UtcDate::at

fn UtcDate::at(self : UtcDate, hour : Int, minute : Int) -> Result[UtcDateTime, CronError]

Create a date-time on this date with a validated hour and minute.

#
UtcDate::days_until

fn UtcDate::days_until(self : UtcDate, other : UtcDate) -> Int

Number of whole calendar days from self to other.

#
UtcDate::end

fn UtcDate::end(self : UtcDate) -> UtcDateTime

Last minute of this calendar date.

#
UtcDate::new

fn UtcDate::new(year : Int, month : Int, day : Int) -> Result[UtcDate, CronError]

Construct a validated UTC calendar date.

#
UtcDate::start

fn UtcDate::start(self : UtcDate) -> UtcDateTime

Start of this calendar date.

#
UtcDate::to_iso8601

fn UtcDate::to_iso8601(self : UtcDate) -> String

ISO calendar-date representation.

#
UtcDateTime

pub struct UtcDateTime {
year : Int
month : Int
day : Int
hour : Int
minute : Int
} derive(Compare, Eq,
Debug
)

A calendar date and wall-clock time in UTC at minute precision. Values are validated on construction, so an instance always denotes a real calendar minute. Ordering compares chronologically.

#
UtcDateTime::add_days

fn UtcDateTime::add_days(self : UtcDateTime, amount : Int) -> UtcDateTime?

Add signed calendar days while preserving the wall-clock time.

#
UtcDateTime::add_minutes

fn UtcDateTime::add_minutes(self : UtcDateTime, amount : Int) -> UtcDateTime?

Add signed minutes, carrying across all calendar boundaries.

#
UtcDateTime::date

fn UtcDateTime::date(self : UtcDateTime) -> UtcDate

Convert a date-time to its calendar date.

#
UtcDateTime::minutes_until

fn UtcDateTime::minutes_until(self : UtcDateTime, other : UtcDateTime) -> Int

Signed minute distance from self to other.

Intended for operational windows rather than the entire 9999-year range.

#
UtcDateTime::new

fn UtcDateTime::new(year : Int, month : Int, day : Int, hour : Int, minute : Int) -> Result[UtcDateTime, CronError]

Build a validated UTC date-time. Years 1 through 9999 are supported.

#
UtcDateTime::to_iso8601

fn UtcDateTime::to_iso8601(self : UtcDateTime) -> String

ISO-like UTC representation at minute precision.

#
UtcDateTime::to_utc_time

fn UtcDateTime::to_utc_time(self : UtcDateTime) -> UtcTime

Project onto the year-less wall-clock view used by Cron::matches, with the weekday computed from the date.

#
UtcDateTime::weekday

fn UtcDateTime::weekday(self : UtcDateTime) -> Int

Weekday of this date: Sunday is 0, Saturday is 6.

#
UtcTime

pub struct UtcTime {
minute : Int
hour : Int
day_of_month : Int
month : Int
weekday : Int
} derive(Eq,
Debug
)

A clock value expressed in UTC. weekday uses cron's conventional values: Sunday is 0 and Saturday is 6.

#
UtcTime::new

fn UtcTime::new(minute : Int, hour : Int, day_of_month : Int, month : Int, weekday : Int) -> UtcTime

#
WeekdayBucket

pub struct WeekdayBucket {
weekday : Int
count : Int
} derive(Eq,
Debug
)

Number of occurrences assigned to one UTC weekday.

#
analyze_collision

fn analyze_collision(left : NamedSchedule, right : NamedSchedule, range : DateTimeRange, limit? : Int) -> CollisionStatistics

#
analyze_schedule

fn analyze_schedule(schedule : NamedSchedule, range : DateTimeRange, limit? : Int) -> ScheduleStatistics

Analyze one named schedule in an inclusive operational window.

#
daily_at

fn daily_at(hour : Int, minute : Int) -> Result[Cron, CronError]

A schedule that fires once a day at the given wall-clock time.

#
days_in_month

fn days_in_month(year : Int, month : Int) -> Int

Number of days in a month, or 0 when the month is outside 1 to 12.

#
every_minute

fn every_minute() -> Cron

A schedule that fires every minute.

#
hourly

fn hourly() -> Cron

A schedule that fires at minute zero of every hour.

#
is_leap_year

fn is_leap_year(year : Int) -> Bool

True for Gregorian leap years.

#
monthly_on

fn monthly_on(day : Int, hour : Int, minute : Int) -> Result[Cron, CronError]

A schedule that fires once a month on the given day. Days 29 through 31 simply skip months that are too short, as in standard cron.

#
parse

fn parse(expression : String) -> Result[Cron, CronError]

Parse the portable cron subset: five whitespace-separated fields built from *, numbers, month and weekday names, ranges (a-b), steps (*/n, a-b/n, a/n) and comma lists, plus @hourly-style macros.

#
parse_crontab

fn parse_crontab(text : String) -> Result[CrontabDocument, CronError]

Parse a portable crontab document. Each error carries the one-based source line number needed by editors and CI diagnostics.

#
parse_utc_date

fn parse_utc_date(text : String) -> Result[UtcDate, CronError]

Parse YYYY-MM-DD.

#
parse_utc_datetime

fn parse_utc_datetime(text : String) -> Result[UtcDateTime, CronError]

Parse YYYY-MM-DDTHH:MMZ.

#
weekday_hourly

fn weekday_hourly() -> Cron

A schedule that fires at minute zero on every weekday hour.

#
weekly_on

fn weekly_on(weekday : Int, hour : Int, minute : Int) -> Result[Cron, CronError]

A schedule that fires once a week. The weekday accepts 0 through 7; both 0 and 7 mean Sunday and are stored canonically as 0.