Scheduling work with cron
Cron and Schedule.cron work in Effeck exactly as in Effect. Use them for work tied to calendar dates, such as a weekday report, and use interval schedules when only elapsed time matters.
Defining calendar execution
const weekdayReport = Schedule.cron("45 7 * * 1-5", "UTC")
function reportJob() {
Effect.schedule(logInfo("generating daily report"), weekdayReport)
}The five fields are minute, hour, day of month, month, and weekday. This rule runs at 07:45 on Monday through Friday; an optional leading seconds field makes 0 45 7 * * 1-5 equivalent. Fields accept *, ranges, comma separated lists, and steps such as */10.
When both day of month and weekday restrict dates, either field can match. For a weekday rule, leave day of month as *. Effect.schedule waits for the first match, so the report does not run immediately at startup.
Selecting a time zone
const localMorning = Schedule.cron("45 7 * * 1-5", "America/Los_Angeles")
const fixedUtc = Schedule.cron("45 7 * * 1-5", "UTC")A named zone follows its local calendar, including daylight saving changes. UTC keeps the same offset all year. Test dates near clock changes, especially for times that disappear or occur twice, and supply the zone explicitly instead of depending on the machine's location.
Validating expressions
function validateRule(expression: string, timeZone: string) {
return Effect.fromResult(Cron.parse(expression, timeZone))
}
// Cron.Cron!Cron.CronParseError!Cron.parse returns a Result, so parsing alone does not throw a typed failure. Effect.fromResult places invalid expressions and invalid zones in the error channel. Validate configuration before starting any work, and report the configuration error at the application boundary.
Previewing future runs
function preview() {
const rule = Effect.fromResult(Cron.parse("45 7 * * 1-5", "UTC"))
let after = new Date("2026-11-02T00:00:00Z")
const dates: string[] = []
for (let i = 0; i < 3; i++) {
after = Cron.next(rule, after)
dates.push(after.toISOString())
}
return dates
}Cron.next finds the next matching instant after the supplied date. Explicit dates make a preview reproducible; this one returns the mornings of November 2, 3, and 4. Cron.sequence also offers an iterator over future matches, and Cron.match tests an existing date against a rule.
Starting and stopping a job
function runApplication() {
const rule = Effect.fromResult(Cron.parse("*/10 * * * *", "UTC"))
const reports = fork(() => Effect.schedule(
logInfo("report tick"),
Schedule.cron(rule)
))
sleep("30 seconds") // Stand-in for the application's active lifetime.
reports.interrupt()
}Keep the job's fiber alive for the part of the application that needs it. Fiber.interrupt waits for cleanup, and block ownership also stops this child when runApplication exits. A failure stops the schedule unless the scheduled action handles it or retries internally.
Cron jobs here exist only in memory. Use an external scheduler or durable pending-work records when a job must survive restarts or recover missed dates.