Complete API documentation for all exported types, functions, and methods
- Package cron
- Types
- Cron
- Entry
- EntryID
- Option
- JobOption
- Schedule
- ScheduleWithPrev
- SpecSchedule
- ConstantDelaySchedule
- Parser
- ParseOption
- Chain
- JobWrapper
- Job
- FuncJob
- JobWithContext
- FuncJobWithContext
- ErrorJob
- FuncErrorJob
- NamedJob
- MissedPolicy
- TriggerCondition
- JobResult
- Dependency
- WorkflowExecution
- Workflow
- ObservabilityHooks
- SpecAnalysis
- ValidationError
- PanicError
- Logger
- Clock
- Timer
- FakeClock
- RealClock
- Functions
- Constants
- Errors
import "github.com/netresearch/go-cron"Package cron implements a cron spec parser and job runner.
type Cron struct {
// contains filtered or unexported fields
}Cron keeps track of any number of entries, invoking the associated func as specified by the schedule. It may be started, stopped, and the entries may be inspected while running.
Entries are stored in a min-heap ordered by next execution time, providing O(log n) insertion/removal and O(1) access to the next entry to run.
func New(opts ...Option) *CronNew returns a new Cron job runner, modified by the given options.
Default Settings:
- Time Zone:
time.Local - Parser: Standard 5-field cron (minute, hour, dom, month, dow)
- Chain: Recovers panics and logs to stderr
- Clock:
RealClock{}(real system time)
Example:
c := cron.New()
c := cron.New(cron.WithSeconds())
c := cron.New(cron.WithLocation(time.UTC))func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)AddFunc adds a func to the Cron to be run on the given schedule. The spec is parsed using the time zone of this Cron instance as the default. An opaque ID is returned that can be used to later remove it.
Example:
id, err := c.AddFunc("30 * * * *", func() {
fmt.Println("Every hour on the half hour")
})func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error)AddJob adds a Job to the Cron to be run on the given schedule. The spec is parsed using the time zone of this Cron instance as the default. An opaque ID is returned that can be used to later remove it.
Example:
type MyJob struct{}
func (j MyJob) Run() { fmt.Println("Running") }
id, err := c.AddJob("@hourly", MyJob{})func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryIDSchedule adds a Job to the Cron to be run on the given schedule. The job is wrapped with the configured Chain.
Example:
id := c.Schedule(cron.Every(time.Hour), cron.FuncJob(func() {
fmt.Println("Every hour")
}))func (c *Cron) UpdateJob(id EntryID, spec string) errorUpdateJob updates the schedule of an existing entry, parsing the provided cron spec.
Returns ErrEntryNotFound if the entry does not exist.
Returns a parse error if the spec is invalid for the configured parser.
If the scheduler is running, the update is applied safely via the run loop and takes effect immediately for next-run computation. If stopped, the schedule is updated directly in place.
Example:
id, _ := c.AddFunc("0 9 * * *", dailyReport)
// Change to run at 10:30 every day
if err := c.UpdateJob(id, "30 10 * * *"); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateJobByName(name, spec string) errorUpdateJobByName updates the schedule of an existing entry identified by its name,
parsing the provided cron spec.
Returns ErrEntryNotFound if the entry does not exist.
Returns a parse error if the spec is invalid for the configured parser.
If the scheduler is running, the actual update is delegated to UpdateSchedule
which routes through the run loop safely.
Example:
c.AddFunc("0 9 * * *", dailyReport, cron.WithName("daily-report"))
// Change to run at 10:30 every day
if err := c.UpdateJobByName("daily-report", "30 10 * * *"); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateSchedule(id EntryID, schedule Schedule) errorUpdateSchedule updates the schedule of an existing entry.
Returns ErrEntryNotFound if the entry does not exist.
If the scheduler is running, the entry is re-positioned in the heap according to
its new Next time; if stopped, the new schedule will apply when started.
Notes:
- Preserves
WrappedJob,Job, name, tags, and missed-run settings - Recomputes
Nextfromclock.Now();Previs unchanged
Example:
id, _ := c.AddFunc("0 9 * * *", dailyReport)
// Change to run at 10:30 every day
schedule, _ := cron.ParseStandard("30 10 * * *")
if err := c.UpdateSchedule(id, schedule); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateScheduleByName(name string, schedule Schedule) errorUpdateScheduleByName updates the Schedule of an existing entry identified by its name.
Returns ErrEntryNotFound if the entry does not exist.
Lookup is O(1) via the internal name index. If the scheduler is running,
the actual update is delegated to UpdateSchedule which routes through the
run loop safely.
Example:
c.AddFunc("0 9 * * *", dailyReport, cron.WithName("daily-report"))
// Change to run at 10:30 every day
schedule, _ := cron.ParseStandard("30 10 * * *")
if err := c.UpdateScheduleByName("daily-report", schedule); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateEntry(id EntryID, schedule Schedule, job Job) errorUpdateEntry atomically replaces both the Schedule and the Job of an existing entry. The new job is re-wrapped through the configured Chain, so middleware (Recover, SkipIfStillRunning, etc.) is applied to the replacement job.
When the job is replaced, the old entry's per-entry context is canceled (signaling
any running JobWithContext to stop), and a fresh context is created for the new job.
This eliminates the need for callers to manage their own context.WithCancel per
reschedule.
Returns ErrEntryNotFound if the entry does not exist.
Returns ErrNilJob if job is nil (use UpdateSchedule to update only the schedule).
Concurrency semantics are the same as UpdateSchedule.
Example:
id, _ := c.AddFunc("0 9 * * *", dailyReport)
// Replace both schedule and job atomically
// The old job's context is automatically canceled.
if err := c.UpdateEntry(id, cron.Every(5*time.Second), cron.FuncJob(func() {
doWork()
})); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateEntryByName(name string, schedule Schedule, job Job) errorUpdateEntryByName atomically replaces both the Schedule and the Job of an existing
entry identified by its Name. Lookup is O(1) via the internal name index.
Delegates to UpdateEntry for the actual update.
Returns ErrEntryNotFound if the entry does not exist.
Returns ErrNilJob if job is nil.
Example:
c.AddFunc("0 9 * * *", dailyReport, cron.WithName("daily-report"))
// Replace both schedule and job by name
if err := c.UpdateEntryByName("daily-report", cron.Every(5*time.Second), newJob); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateEntryJob(id EntryID, spec string, job Job) errorUpdateEntryJob parses spec with the Cron's configured parser, then atomically replaces both schedule and job. This eliminates the need for callers to construct their own parser matching the Cron's configuration.
Returns a parse error if spec is invalid for the configured parser.
Returns ErrEntryNotFound if the entry does not exist.
Returns ErrNilJob if job is nil.
Example:
id, _ := c.AddFunc("0 9 * * *", dailyReport)
// Update both schedule and job using a spec string
if err := c.UpdateEntryJob(id, "@every 5m", newJob); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpdateEntryJobByName(name, spec string, job Job) errorUpdateEntryJobByName is the name-based variant of UpdateEntryJob.
Parses spec with the Cron's configured parser, then atomically replaces both
schedule and job of the entry identified by name.
Returns a parse error if spec is invalid for the configured parser.
Returns ErrEntryNotFound if the entry does not exist.
Returns ErrNilJob if job is nil.
Example:
c.AddFunc("0 9 * * *", dailyReport, cron.WithName("daily-report"))
// Update both schedule and job by name using a spec string
if err := c.UpdateEntryJobByName("daily-report", "@every 5m", newJob); err != nil {
log.Println("update failed:", err)
}func (c *Cron) UpsertJob(spec string, cmd Job, opts ...JobOption) (EntryID, error)UpsertJob creates or updates a named job entry. If an entry with the given
name already exists, its schedule and job are atomically replaced via
UpdateEntry. If no entry exists, a new one is created via ScheduleJob.
A WithName option is required. Returns ErrNameRequired if no name is provided.
This eliminates the common "try update, fallback to add" boilerplate:
Example:
// Before (manual upsert):
if err := c.UpdateEntryJobByName(name, spec, job); errors.Is(err, cron.ErrEntryNotFound) {
c.AddJob(spec, job, cron.WithName(name))
}
// After:
id, err := c.UpsertJob(spec, job, cron.WithName(name))func (c *Cron) DrainAndUpsertJob(spec string, cmd Job, opts ...JobOption) (EntryID, error)DrainAndUpsertJob is the windowless variant of UpsertJob. For an existing
named entry it pauses the entry, waits for any in-flight invocation to finish,
replaces the schedule and job, then restores the entry's prior paused state.
Pausing before the drain guarantees the old schedule cannot fire again between
the wait and the replacement — closing the gap present in the two-step
WaitForJobByName + UpsertJob sequence. If no entry with the given name
exists it creates one (no stale schedule ⇒ no window). Same options and errors
as UpsertJob; a WithName option is required.
It blocks for as long as the in-flight invocation runs but holds no cron lock
while draining, so it never blocks the run loop or other mutators. It is not
atomic against other concurrent mutators of the same entry (Remove, Resume,
Trigger); those are handled gracefully but fall outside the no-stale-fire
guarantee. See ADR-022.
Example:
// Graceful, windowless reschedule in one call:
id, err := c.DrainAndUpsertJob(newSpec, newJob, cron.WithName("my-job"))func (c *Cron) WaitForJob(id EntryID)WaitForJob blocks until all currently-running invocations of the given entry complete. Returns immediately if the entry is not currently running or does not exist.
This enables graceful job replacement — wait for the current execution to finish before replacing the job:
cr.WaitForJob(id)
cr.UpsertJob(newSpec, newJob, cron.WithName("my-job"))func (c *Cron) WaitForJobByName(name string)WaitForJobByName is the named variant of WaitForJob. Blocks until all currently-running invocations of the named entry complete. Returns immediately if the entry is not currently running or no entry has the given name.
cr.WaitForJobByName("my-job")
cr.UpsertJob(newSpec, newJob, cron.WithName("my-job"))Note: the two calls above are not atomic — the old schedule can fire once between them. Use
DrainAndUpsertJobfor a windowless reschedule.
func (c *Cron) IsJobRunning(id EntryID) boolIsJobRunning reports whether the entry with the given ID has any invocations currently in flight. Returns false for non-existent entries.
func (c *Cron) IsJobRunningByName(name string) boolIsJobRunningByName reports whether the named entry has any invocations currently in flight. Returns false if no entry has the given name.
if cr.IsJobRunningByName("my-job") {
cr.WaitForJobByName("my-job")
}
cr.UpsertJob(newSpec, newJob, cron.WithName("my-job"))func (c *Cron) PauseEntry(id EntryID) errorPauseEntry temporarily suspends execution of the entry with the given ID.
While paused, the entry remains registered and its schedule advances, but
execution is skipped. Use ResumeEntry to re-enable execution.
Pausing an already-paused entry is a no-op (returns nil).
Returns ErrEntryNotFound if no entry with the given ID exists.
Example:
id, _ := c.AddFunc("@every 5m", syncData, cron.WithName("sync"))
c.Start()
// Pause during maintenance
if err := c.PauseEntry(id); err != nil {
log.Println("pause failed:", err)
}func (c *Cron) PauseEntryByName(name string) errorPauseEntryByName temporarily suspends the entry identified by its Name.
Lookup is O(1) via the internal name index.
Returns ErrEntryNotFound if no entry with the given name exists.
Example:
c.AddFunc("@every 5m", syncData, cron.WithName("sync"))
c.PauseEntryByName("sync")func (c *Cron) ResumeEntry(id EntryID) errorResumeEntry re-enables execution of a previously paused entry. The entry's schedule is preserved; it will execute at its next scheduled time.
Resuming an already-active entry is a no-op (returns nil).
Returns ErrEntryNotFound if no entry with the given ID exists.
Example:
// Resume after maintenance
if err := c.ResumeEntry(id); err != nil {
log.Println("resume failed:", err)
}func (c *Cron) ResumeEntryByName(name string) errorResumeEntryByName re-enables execution of a previously paused entry
identified by its Name. Lookup is O(1) via the internal name index.
Returns ErrEntryNotFound if no entry with the given name exists.
Example:
c.ResumeEntryByName("sync")func (c *Cron) IsEntryPaused(id EntryID) boolIsEntryPaused reports whether the entry with the given ID is currently paused. Returns false if the entry does not exist.
func (c *Cron) IsEntryPausedByName(name string) boolIsEntryPausedByName reports whether the named entry is currently paused. Returns false if no entry has the given name.
Example:
if c.IsEntryPausedByName("sync") {
fmt.Println("sync job is paused")
}func (c *Cron) AddDependency(child, parent EntryID, condition TriggerCondition) errorAddDependency adds a dependency edge: child waits for parent with the given
condition. When the parent job completes, the child is triggered if the condition
matches the parent's JobResult.
The operation is idempotent: adding an identical edge is a no-op.
Returns ErrEntryNotFound if either entry does not exist.
Returns ErrInvalidCondition if the condition is not valid.
Returns ErrCycleDetected if the edge would create a cycle.
Example:
a, _ := c.AddFunc("0 2 * * *", extract, cron.WithName("extract"))
b, _ := c.AddFunc("@triggered", transform, cron.WithName("transform"))
if err := c.AddDependency(b, a, cron.OnSuccess); err != nil {
log.Fatal(err)
}func (c *Cron) AddDependencyByName(child, parent string, condition TriggerCondition) errorAddDependencyByName is the name-based variant of AddDependency.
Returns ErrEntryNotFound if either name does not exist.
Example:
c.AddDependencyByName("transform", "extract", cron.OnSuccess)func (c *Cron) RemoveDependency(child, parent EntryID) errorRemoveDependency removes a dependency edge between child and parent.
func (c *Cron) RemoveDependencyByName(child, parent string) errorRemoveDependencyByName is the name-based variant of RemoveDependency.
Returns ErrEntryNotFound if either name does not exist.
func (c *Cron) Dependencies(id EntryID) []DependencyDependencies returns a copy of the dependency edges for an entry. Returns nil if the entry has no dependencies.
func (c *Cron) DependenciesByName(name string) []DependencyDependenciesByName is the name-based variant of Dependencies.
Returns nil if no entry has the given name or the entry has no dependencies.
func (c *Cron) AddWorkflow(w *Workflow) errorAddWorkflow validates and registers all steps of a Workflow atomically. It parses all specs, checks for duplicate names, validates the DAG structure (no cycles, at most one final step, all After references exist), and then registers all entries and wires dependency edges. On any failure, already-registered entries are rolled back.
Failure model: The workflow engine detects job failure via panics. Since
Job.Run() has no return value, steps that need to signal errors should use
FuncErrorJob (which converts errors to panics) or wrappers like RetryOnError
/ RetryWithBackoff. The Recover wrapper is workflow-aware and re-panics in
workflow context so failures propagate correctly.
Returns:
ErrEmptyWorkflowif the workflow has no stepsErrMultipleFinalStepsif more than one step is marked FinalErrUnknownStepif a step references an unknown parent via AfterErrCycleDetectedif the step dependencies form a cycleErrDuplicateNameif a step name conflicts with an existing entry- Parse errors if any spec is invalid for the configured parser
Example:
wf := cron.NewWorkflow("etl-pipeline")
wf.StepFunc("extract", "0 2 * * *", extractData)
wf.StepFunc("transform", "@triggered", transformData).
After("extract", cron.OnSuccess)
wf.StepFunc("load", "@triggered", loadData).
After("transform", cron.OnSuccess)
wf.StepFunc("cleanup", "@triggered", cleanup).Final()
if err := c.AddWorkflow(wf); err != nil {
log.Fatal(err)
}func (c *Cron) WorkflowStatus(executionID string) *WorkflowExecutionWorkflowStatus returns the execution state for the given workflow execution ID.
It searches active executions first, then completed executions (retained up to
the WithWorkflowRetention limit). Returns nil if no execution with the given
ID exists.
Example:
status := c.WorkflowStatus(execID)
if status != nil && status.IsComplete() {
fmt.Println("Workflow finished at", status.StartTime)
}func (c *Cron) ActiveWorkflows() []WorkflowExecutionActiveWorkflows returns copies of all in-progress workflow executions. Returns an empty slice if no workflows are currently executing.
Example:
for _, wf := range c.ActiveWorkflows() {
fmt.Printf("Workflow %s started at %v\n", wf.ID, wf.StartTime)
}func (c *Cron) TriggerEntry(id EntryID) errorTriggerEntry immediately executes the entry with the given ID, regardless
of its schedule. The entry's middleware chain (Recover, SkipIfStillRunning,
etc.) is applied as usual. This works on both triggered (@triggered) and
regularly scheduled entries — providing a "run now" capability for any entry.
The scheduler must be running; returns ErrNotRunning otherwise.
Returns ErrEntryPaused if the entry is paused.
Returns ErrEntryNotFound if no entry with the given ID exists.
Example:
id, _ := c.AddFunc("@triggered", deploy, cron.WithName("deploy"))
c.Start()
c.TriggerEntry(id) // Run on demandfunc (c *Cron) TriggerEntryByName(name string) errorTriggerEntryByName immediately executes the entry identified by its Name. Lookup is O(1) via the internal name index.
Returns ErrNotRunning if the scheduler is not running.
Returns ErrEntryPaused if the entry is paused.
Returns ErrEntryNotFound if no entry with the given name exists.
Example:
c.AddFunc("@triggered", deploy, cron.WithName("deploy"))
c.Start()
c.TriggerEntryByName("deploy")func (c *Cron) ScheduleJob(schedule Schedule, cmd Job, opts ...JobOption) (EntryID, error)ScheduleJob adds a Job to the Cron to be run on the given schedule.
The job is wrapped with the configured Chain. Unlike Schedule, returns an error
instead of silently logging on failure, and supports JobOption arguments.
Returns ErrMaxEntriesReached if the maximum entry limit has been reached.
Returns ErrDuplicateName if a name is provided and already exists.
func (c *Cron) AddOnceFunc(spec string, cmd func(), opts ...JobOption) (EntryID, error)AddOnceFunc adds a func to run once on the given schedule, then automatically
remove itself. Convenience wrapper combining AddFunc with WithRunOnce().
func (c *Cron) AddOnceJob(spec string, cmd Job, opts ...JobOption) (EntryID, error)AddOnceJob adds a Job to run once on the given schedule, then automatically
remove itself. Convenience wrapper combining AddJob with WithRunOnce().
func (c *Cron) ScheduleOnceJob(schedule Schedule, cmd Job, opts ...JobOption) (EntryID, error)ScheduleOnceJob adds a Job to run once on the given schedule, then automatically
remove itself. Convenience wrapper combining ScheduleJob with WithRunOnce().
func (c *Cron) ValidateSpec(spec string) errorValidateSpec validates a cron expression using this Cron instance's configured parser. Returns nil if valid, or an error describing the problem. Useful for pre-validating user input when the Cron uses a custom parser.
Example:
c := cron.New(cron.WithSeconds())
if err := c.ValidateSpec("0 30 * * * *"); err != nil {
return fmt.Errorf("invalid cron expression: %w", err)
}func (c *Cron) Entries() []EntryEntries returns a snapshot of the cron entries, sorted by next execution time.
Each returned Entry is a struct copy with its Tags slice cloned, so mutating
the returned tags does not affect internal scheduler state. Other reference-typed
fields (e.g., Schedule, Job) are shallow-copied.
func (c *Cron) Entry(id EntryID) EntryEntry returns a snapshot of the given entry, or a zero Entry if not found.
This operation is O(1) using the internal index map. The returned Entry is a
struct copy with its Tags slice cloned, so mutating the returned tags does
not affect internal scheduler state.
func (c *Cron) EntryByName(name string) EntryEntryByName returns a snapshot of the entry with the given name,
or an invalid Entry (Entry.Valid() == false) if not found.
This operation is O(1) using the internal name index. The returned Entry is a
struct copy with its Tags slice cloned, so mutating the returned tags does
not affect internal scheduler state.
Example:
c.AddFunc("0 9 * * *", job, cron.WithName("daily-report"))
entry := c.EntryByName("daily-report")
if entry.Valid() {
fmt.Println("Next run:", entry.Next)
}func (c *Cron) EntriesByTag(tag string) []EntryEntriesByTag returns snapshots of all entries that have the given tag. Returns an empty slice if no entries match.
Example:
c.AddFunc("0 9 * * *", job1, cron.WithTags("reports"))
c.AddFunc("0 * * * *", job2, cron.WithTags("reports"))
entries := c.EntriesByTag("reports") // Returns both entriesfunc (c *Cron) Remove(id EntryID)Remove an entry from being run in the future. Cancels the entry's per-entry context.
func (c *Cron) RemoveByName(name string) boolRemoveByName removes the entry with the given name. Returns true if an entry was removed, false if no entry had that name.
func (c *Cron) RemoveByTag(tag string) intRemoveByTag removes all entries that have the given tag. Returns the number of entries removed.
func (c *Cron) IsRunning() boolIsRunning returns true if the cron scheduler is currently running.
func (c *Cron) Start()Start the cron scheduler in its own goroutine, or no-op if already started.
func (c *Cron) Run()Run the cron scheduler in the current goroutine (blocking), or no-op if already running.
func (c *Cron) Stop() context.ContextStop stops the cron scheduler if it is running; otherwise it does nothing. A context is returned so the caller can wait for running jobs to complete.
Example:
ctx := c.Stop()
<-ctx.Done() // Wait for all jobs to finishfunc (c *Cron) StopAndWait()StopAndWait stops the cron scheduler and blocks until all running jobs complete.
Equivalent to <-c.Stop().Done().
func (c *Cron) StopWithTimeout(timeout time.Duration) boolStopWithTimeout stops the cron scheduler and waits for running jobs to complete with a timeout. Returns true if all jobs completed within the timeout, false if the timeout was reached. A timeout of zero or negative waits indefinitely.
Example:
if !c.StopWithTimeout(30 * time.Second) {
log.Println("Warning: some jobs did not complete within 30s")
}func (c *Cron) Location() *time.LocationLocation gets the time zone location.
type Entry struct {
ID EntryID // Cron-assigned unique identifier
Schedule Schedule // Schedule for this entry
Next time.Time // Next activation time (zero if not started)
Prev time.Time // Last run time (zero if never run)
WrappedJob Job // Job with chain wrappers applied
Job Job // Original job as submitted
Name string // Optional name for the entry
Tags []string // Optional tags for categorizing entries
MissedPolicy MissedPolicy // Policy for handling missed executions
MissedGracePeriod time.Duration // Maximum age for catch-up runs
Paused bool // Whether this entry is paused
Triggered bool // Whether this entry uses a triggered schedule
Priority int // Optional tiebreaker for entries due at the same time (higher starts first)
}Entry consists of a schedule and the func to execute on that schedule.
Per-entry context: Each entry has its own context.Context derived from the
Cron's base context. Jobs implementing JobWithContext receive this per-entry
context, enabling fine-grained cancellation:
- Remove/RemoveByName: cancels the removed entry's context
- UpdateEntry (with new job): cancels the old context, creates a fresh one
- UpdateSchedule (schedule-only): does NOT cancel the context
- Stop(): cancels the base context, which cascades to all entry contexts
func (e Entry) Valid() boolValid returns true if this is not the zero entry.
func (e Entry) Run()Run executes the entry's job through the configured chain wrappers.
Use this instead of Entry.Job.Run() when chain behavior is needed.
type EntryID uint64EntryID identifies an entry within a Cron instance. Using uint64 prevents overflow and ID collisions on all platforms.
type MissedPolicy int
const (
MissedSkip MissedPolicy = iota // Do not catch up on missed executions (default)
MissedRunOnce // Run once for the most recent missed execution
MissedRunAll // Run for every missed execution (capped at 100)
)MissedPolicy defines how to handle jobs that were scheduled to run while the scheduler was not running (e.g., application restart).
Important: This feature requires the user to provide the last run time via
WithPrev(). The scheduler does NOT persist state - users are responsible for
storing and loading last run times from their own persistence layer.
Policies:
MissedSkip: Default behavior. No catch-up runs occur.MissedRunOnce: Run the job once immediately for the most recent missed time.MissedRunAll: Run the job for every missed execution (up to 100 runs for safety).
Example:
// Load last run time from your database
lastRun := loadFromDatabase("daily-report")
c.AddFunc("0 9 * * *", dailyReport,
cron.WithPrev(lastRun), // When it last ran
cron.WithMissedPolicy(cron.MissedRunOnce), // Run once if missed
cron.WithMissedGracePeriod(2*time.Hour), // Only if within 2 hours
)See PERSISTENCE_GUIDE.md for complete integration patterns.
type JobOption func(*Entry)JobOption represents a modification to a specific job entry.
Job options are passed to AddFunc, AddJob, or Schedule methods.
func WithName(name string) JobOptionWithName sets a name for the job entry. Named jobs can be looked up via
EntryByName() and filtered via EntriesByTag().
Example:
c.AddFunc("0 9 * * *", dailyReport, cron.WithName("daily-report"))
entry := c.EntryByName("daily-report")func WithPrev(t time.Time) JobOptionWithPrev sets the last execution time for the job entry. This is used to
calculate missed executions when combined with WithMissedPolicy().
Example:
lastRun := loadFromDatabase("my-job")
c.AddFunc("0 * * * *", myJob,
cron.WithPrev(lastRun),
cron.WithMissedPolicy(cron.MissedRunOnce),
)func WithMissedPolicy(policy MissedPolicy) JobOptionWithMissedPolicy sets the policy for handling missed job executions.
See MissedPolicy for available options.
func WithMissedGracePeriod(d time.Duration) JobOptionWithMissedGracePeriod sets the maximum age for missed executions to be
caught up. Missed runs older than this duration are skipped even with
MissedRunAll policy.
Example:
// Only catch up on missed runs from the last hour
c.AddFunc("*/5 * * * *", job,
cron.WithPrev(lastRun),
cron.WithMissedPolicy(cron.MissedRunAll),
cron.WithMissedGracePeriod(time.Hour),
)func WithTags(tags ...string) JobOptionWithTags sets tags for categorizing the job entry. Multiple entries can share the same tags, enabling group operations.
Example:
c.AddFunc("* * * * *", job1, cron.WithTags("reports", "daily"))
c.AddFunc("0 * * * *", job2, cron.WithTags("reports", "hourly"))
entries := c.EntriesByTag("reports")type Option func(*Cron)Option represents a modification to the default behavior of a Cron.
func WithLocation(loc *time.Location) OptionWithLocation overrides the timezone of the cron instance.
func WithSeconds() OptionWithSeconds overrides the parser to include a seconds field as the first field. Equivalent to Quartz scheduler format.
func WithParser(p ScheduleParser) OptionWithParser overrides the parser used for interpreting job schedules.
func WithChain(wrappers ...JobWrapper) OptionWithChain specifies Job wrappers to apply to all jobs added to this cron.
func WithLogger(logger Logger) OptionWithLogger uses the provided logger.
func WithClock(clock Clock) OptionWithClock uses the provided Clock implementation instead of RealClock. Useful for testing time-dependent behavior without waiting.
Example:
// For testing with FakeClock
fakeClock := cron.NewFakeClock(time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC))
c := cron.New(cron.WithClock(fakeClock))
c.Start()
fakeClock.Advance(time.Hour) // Trigger jobs deterministically
// RealClock is used by default, no need to specify unless overridingfunc WithContext(ctx context.Context) OptionWithContext sets the parent context from which the cron scheduler derives its
own cancelable child context (via context.WithCancel) for all job executions.
Stop() cancels only that derived child context, signaling all running
JobWithContext jobs to shut down; the caller-provided context is not canceled
by Stop(). If not specified, context.Background() is used as the parent.
Example:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := cron.New(cron.WithContext(ctx))func WithMaxEntries(maxEntries int) OptionWithMaxEntries limits the maximum number of entries. When the limit is reached,
AddFunc and AddJob return ErrMaxEntriesReached. A limit of 0 means unlimited.
func WithObservability(hooks ObservabilityHooks) OptionWithObservability configures observability hooks for monitoring cron operations.
func WithSecondOptional() OptionWithSecondOptional overrides the parser to accept an optional seconds field. Expressions can have either 5 fields (standard) or 6 fields (with seconds).
func WithMinEveryInterval(d time.Duration) OptionWithMinEveryInterval configures the minimum interval allowed for @every expressions.
Default is 1 second. Set to 0 for sub-second intervals (useful for testing).
func WithMaxSearchYears(years int) OptionWithMaxSearchYears configures how far into the future schedule matching searches before giving up. Default is 5 years.
func WithRunImmediately() JobOptionWithRunImmediately causes the job to run immediately upon registration, then follow the normal schedule thereafter.
func WithRunOnce() JobOptionWithRunOnce causes the job to be automatically removed after its first execution.
func WithPaused() JobOptionWithPaused causes the entry to be added in a paused state. Paused entries remain
registered with their schedule intact but are skipped during execution. Use
ResumeEntry or ResumeEntryByName to activate the entry later.
This is useful for pre-registering jobs that should only run after explicit activation, maintenance windows, or feature-flagged jobs.
Example:
id, _ := c.AddFunc("@every 5m", syncData, cron.WithPaused(), cron.WithName("sync"))
// Later, when ready:
c.ResumeEntry(id)func WithCapacity(n int) OptionWithCapacity pre-allocates internal data structures for the expected number of entries. This reduces map rehashing and slice growth during bulk additions, improving performance when adding many jobs at startup.
Pre-allocates:
entryIndexmap with capacity n (O(1) lookup by ID)nameIndexmap with capacity n (O(1) lookup by name)entriesheap slice with capacity n
For applications adding fewer than 100 jobs, the default allocation is sufficient. Use this option when bulk-loading hundreds or thousands of jobs.
A capacity of 0 or negative has no effect (uses default allocation).
Example:
// Expect ~1000 jobs at startup
c := cron.New(cron.WithCapacity(1000))
for _, job := range jobs {
c.AddFunc(job.Schedule, job.Func)
}func WithWorkflowRetention(n int) OptionWithWorkflowRetention sets the maximum number of completed workflow executions
to retain for query via WorkflowStatus. Default is 100. Set to 0 for unlimited
retention (not recommended for long-running services).
Example:
c := cron.New(cron.WithWorkflowRetention(50))func WithPriority(p int) JobOptionWithPriority sets the Priority of the entry. Priority breaks ties when several entries are due at the same time: the run loop starts higher-priority jobs first. Higher is better; the default is 0.
The guarantee is limited to the order in which job goroutines are started.
Actual execution and completion order is decided by the Go scheduler, and chain
wrappers such as Jitter or SkipIfStillRunning decouple it further.
Priority is applied at add time. UpdateEntry and UpdateSchedule preserve
it; UpsertJob's update path ignores JobOptions, which is the existing
behavior for every option.
Example:
id1, _ := c.AddFunc("0 * * * *", criticalJob, cron.WithPriority(100))
id2, _ := c.AddFunc("0 * * * *", normalJob, cron.WithPriority(50))
id3, _ := c.AddFunc("0 * * * *", lowPriorityJob, cron.WithPriority(25))
id4, _ := c.AddFunc("0 * * * *", defaultJob) // Default priority: 0.type TriggerCondition int
const (
OnSuccess TriggerCondition = iota // Parent completed without panicking
OnFailure // Parent panicked (use FuncErrorJob to convert errors)
OnSkipped // Parent was skipped (condition not met)
OnComplete // Parent resolved to any terminal state
)TriggerCondition defines when a dependent job should be triggered relative to
its parent's outcome. Used with AddDependency and the Workflow builder's
After method.
func (c TriggerCondition) String() stringString returns the human-readable name ("OnSuccess", "OnFailure", "OnSkipped", "OnComplete").
func (c TriggerCondition) Valid() boolValid reports whether c is a known trigger condition.
func (c TriggerCondition) Matches(result JobResult) boolMatches reports whether the given parent result satisfies this condition.
OnComplete matches any terminal result; the others match their specific JobResult.
type JobResult int
const (
ResultPending JobResult = iota // Job has not yet completed
ResultSuccess // Job completed without error
ResultFailure // Job failed (error or panic)
ResultSkipped // Job was skipped (condition not met)
)JobResult represents the outcome of a job within a workflow execution.
func (r JobResult) String() stringString returns the human-readable name ("Pending", "Success", "Failure", "Skipped").
func (r JobResult) IsTerminal() boolIsTerminal reports whether the result represents a final state. Returns true for
ResultSuccess, ResultFailure, and ResultSkipped; false for ResultPending.
type Dependency struct {
ParentID EntryID
Condition TriggerCondition
}Dependency represents a directed edge in the workflow DAG. The child entry
waits for ParentID to resolve, then fires if Condition matches the parent's
JobResult. Returned by Dependencies and DependenciesByName.
type WorkflowExecution struct {
ID string
RootID EntryID
StartTime time.Time
Results map[EntryID]JobResult
}WorkflowExecution tracks the state of a single workflow run. RootID is the
entry whose completion triggered the execution. Results maps each participating
entry to its current JobResult.
func (we *WorkflowExecution) IsComplete() boolIsComplete reports whether every job in the execution has reached a terminal state.
type Workflow struct {
Name string
// contains filtered or unexported fields
}Workflow defines a multi-step DAG of named jobs with dependency edges. Use
NewWorkflow to create a workflow, then Step/StepFunc to add steps, and
AddWorkflow on a Cron instance to register it atomically.
func NewWorkflow(name string) *WorkflowNewWorkflow creates a new Workflow with the given name.
Example:
wf := cron.NewWorkflow("etl-pipeline")
wf.StepFunc("extract", "0 2 * * *", extractData)
wf.StepFunc("transform", "@triggered", transformData).
After("extract", cron.OnSuccess)
err := c.AddWorkflow(wf)func (w *Workflow) Step(name, spec string, job Job) *WorkflowStepStep adds a named step to the workflow with the given schedule spec and Job.
func (w *Workflow) StepFunc(name, spec string, fn func()) *WorkflowStepStepFunc adds a named step with a plain function as its job.
func (s *WorkflowStep) After(parentName string, condition TriggerCondition) *WorkflowStepAfter declares that this step depends on the named parent step with the given
condition. Multiple After calls can be chained to create fan-in dependencies.
Example:
wf.StepFunc("load", "@triggered", loadData).
After("transform", cron.OnSuccess).
After("validate", cron.OnSuccess) // fan-in: both must succeedfunc (s *WorkflowStep) Final() *WorkflowStepFinal marks this step as a finalization step. A final step receives an
OnComplete edge from every non-final step, ensuring it runs after all other
steps have resolved regardless of their outcome. At most one step per workflow
may be marked Final.
type Schedule interface {
Next(time.Time) time.Time
}Schedule describes a job's duty cycle. Implementations must return the next activation time, later than the given time.
type ScheduleWithPrev interface {
Schedule
Prev(time.Time) time.Time
}ScheduleWithPrev is an optional interface that schedules can implement to support backward time traversal. This is useful for detecting missed executions or determining the last scheduled run time.
Built-in schedules (SpecSchedule, ConstantDelaySchedule) implement this interface.
Custom Schedule implementations may optionally implement it.
Usage:
schedule, _ := cron.ParseStandard("0 9 * * *")
// Type assert to access Prev()
if sp, ok := schedule.(cron.ScheduleWithPrev); ok {
prev := sp.Prev(time.Now())
fmt.Println("Last scheduled run:", prev)
}type ScheduleParser interface {
Parse(spec string) (Schedule, error)
}ScheduleParser is an interface for schedule spec parsers.
type SpecSchedule struct {
Second, Minute, Hour, Dom, Month, Dow uint64
Location *time.Location
}SpecSchedule represents a cron schedule defined by bit fields for each time component.
func (s *SpecSchedule) Next(t time.Time) time.TimeNext returns the next activation time after the given time. Returns zero time if no valid time exists.
type ConstantDelaySchedule struct {
Delay time.Duration
}ConstantDelaySchedule represents a simple recurring duty cycle.
func Every(duration time.Duration) ConstantDelayScheduleEvery returns a crontab Schedule that activates once every duration. Delays of less than 1 second are not supported (rounds up to 1 second).
Example:
c.Schedule(cron.Every(5*time.Minute), job)func (s ConstantDelaySchedule) Next(t time.Time) time.TimeNext returns the next activation time after t.
type TriggeredSchedule struct{}TriggeredSchedule is a schedule that never fires automatically. Entries using
this schedule remain dormant until explicitly triggered via TriggerEntry or
TriggerEntryByName. Created by the @triggered, @manual, or @none descriptors.
func (TriggeredSchedule) Next(time.Time) time.TimeNext always returns the zero time.
func (TriggeredSchedule) Prev(time.Time) time.TimePrev always returns the zero time.
func IsTriggered(s Schedule) boolIsTriggered reports whether the given schedule is a TriggeredSchedule.
Example:
if cron.IsTriggered(entry.Schedule) {
fmt.Println("This entry only runs when triggered manually")
}type Parser struct {
// contains filtered or unexported fields
}Parser parses cron specs into Schedule objects.
func NewParser(options ParseOption) ParserNewParser creates a Parser with custom options.
Example:
// Quartz-style with seconds
parser := cron.NewParser(
cron.Second | cron.Minute | cron.Hour |
cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)func (p Parser) Parse(spec string) (Schedule, error)Parse returns a Schedule from a cron spec or error if invalid.
type ParseOption int
const (
Second ParseOption = 1 << iota // Seconds field, required
SecondOptional // Seconds field, optional
Minute // Minutes field
Hour // Hours field
Dom // Day of month field
Month // Month field
Dow // Day of week field
DowOptional // Day of week, optional
Descriptor // Enable @hourly, @every, etc.
)ParseOption represents parser configuration flags.
type Chain struct {
// contains filtered or unexported fields
}Chain is a sequence of JobWrappers.
func NewChain(c ...JobWrapper) ChainNewChain returns a Chain of the given JobWrappers.
func (c Chain) Then(j Job) JobThen applies all wrappers to the given job and returns the wrapped job.
type JobWrapper func(Job) JobJobWrapper is a function that wraps a Job with additional behavior.
All chain wrappers implement JobWithContext and propagate the incoming context
to inner jobs that also implement JobWithContext. This means per-entry context
flows through the entire wrapper chain to context-aware jobs.
func RunJob(ctx context.Context, j Job)RunJob executes the job. If the job implements JobWithContext, it is called
with the provided context. Otherwise, its Run method is called.
This is a helper intended for use in custom JobWrapper implementations.
func Recover(logger Logger, opts ...RecoverOption) JobWrapperRecover catches panics in jobs, logs them, and continues. Propagates context to context-aware inner jobs.
Workflow-aware: When running inside a workflow execution, Recover logs the panic and then re-panics so the workflow engine correctly detects the failure. The scheduler catches the re-panic without crashing.
func SkipIfStillRunning(logger Logger) JobWrapperSkipIfStillRunning skips a job invocation if the previous one is still running. Propagates context to context-aware inner jobs.
func DelayIfStillRunning(logger Logger) JobWrapperDelayIfStillRunning delays a job invocation until the previous one completes. Propagates context to context-aware inner jobs.
func Timeout(logger Logger, timeout time.Duration, opts ...TimeoutOption) JobWrapperTimeout wraps a job with a timeout using the abandonment model. Propagates context to context-aware inner jobs.
Example:
c := cron.New(cron.WithChain(
cron.Timeout(logger, 30*time.Second),
cron.Recover(logger),
))func Jitter(maxJitter time.Duration) JobWrapperJitter adds a random delay before job execution to prevent thundering herd. Propagates context to context-aware inner jobs.
func JitterWithLogger(logger Logger, maxJitter time.Duration) JobWrapperJitterWithLogger is like Jitter but logs the applied delay. Propagates context to context-aware inner jobs.
func MaxConcurrent(n int) JobWrapperMaxConcurrent limits the total number of jobs that can run concurrently across all entries wrapped by this chain. When all slots are occupied, new job executions wait until a slot becomes available or the context is canceled.
Note: waiting goroutines still accumulate. Use MaxConcurrentSkip to drop excess
executions instead. Unlike SkipIfStillRunning (per-job), this limits across all
jobs sharing the same wrapper instance. Panics if n <= 0. Propagates context to
context-aware inner jobs.
Example:
c := cron.New(cron.WithChain(
cron.Recover(logger),
cron.MaxConcurrent(10),
))func MaxConcurrentSkip(logger Logger, n int) JobWrapperMaxConcurrentSkip is like MaxConcurrent but skips execution instead of
waiting when the concurrency limit is reached. Logs skips at Info level.
Panics if n <= 0. Propagates context to context-aware inner jobs.
Example:
c := cron.New(cron.WithChain(
cron.Recover(logger),
cron.MaxConcurrentSkip(logger, 5),
))func RetryWithBackoff(logger Logger, maxRetries int, initialDelay, maxDelay time.Duration, multiplier float64, opts ...RetryOption) JobWrapperRetryWithBackoff wraps a job to retry on panic with exponential backoff.
maxRetries: 0 = no retries, >0 = retry up to N times, -1 = unlimitedinitialDelay: First retry delaymaxDelay: Maximum delay capmultiplier: Delay multiplier per retry (typically 2.0)opts: OptionalRetryOptionvalues (e.g.,WithRetryCallback)
Jitter of +/-10% is applied to prevent thundering herd.
Example:
c := cron.New(cron.WithChain(
cron.Recover(logger),
cron.RetryWithBackoff(logger, 3, time.Second, time.Minute, 2.0),
))func RetryOnError(logger Logger, maxRetries int, initialDelay, maxDelay time.Duration, multiplier float64, opts ...RetryOption) JobWrapperRetryOnError wraps an ErrorJob to retry on returned errors with exponential backoff.
Unlike RetryWithBackoff which catches panics, this wrapper uses Go-idiomatic error
returns. Jobs must implement ErrorJob; regular Job implementations are passed through
unchanged.
Example:
c := cron.New(cron.WithChain(
cron.Recover(logger),
cron.RetryOnError(logger, 3, time.Second, time.Minute, 2.0),
))
c.AddJob("@every 5m", cron.FuncErrorJob(func() error {
return callAPI()
}))type RetryAttempt struct {
Attempt int // 1-based attempt number
Delay time.Duration // Delay before this attempt (0 for first)
Err any // Panic value (RetryWithBackoff) or error (RetryOnError)
WillRetry bool // True if another attempt will follow
}RetryAttempt contains metadata about a single retry attempt, passed to the
callback configured via WithRetryCallback.
type RetryOption func(*retryConfig)RetryOption configures optional behavior for RetryWithBackoff and RetryOnError.
func WithRetryCallback(fn func(RetryAttempt)) RetryOptionWithRetryCallback sets a callback invoked after each attempt (including the initial execution). Use this for metrics, alerting, or debugging retry behavior.
Example with Prometheus:
cron.RetryWithBackoff(logger, 3, time.Second, time.Minute, 2.0,
cron.WithRetryCallback(func(a cron.RetryAttempt) {
retryCounter.WithLabelValues(fmt.Sprint(a.Attempt)).Inc()
if !a.WillRetry && a.Err != nil {
retryExhausted.Inc()
}
}),
)func CircuitBreaker(logger Logger, threshold int, cooldown time.Duration, opts ...CircuitBreakerOption) JobWrapperCircuitBreaker wraps a job to stop execution after consecutive failures.
- Closed: Normal execution. Failures increment counter.
- Open: Execution skipped for
cooldownduration afterthresholdfailures. - Half-Open: After cooldown, one execution attempted. Success closes, failure reopens.
opts: OptionalCircuitBreakerOptionvalues (e.g.,WithStateChangeCallback)
Example:
c := cron.New(cron.WithChain(
cron.Recover(logger),
cron.CircuitBreaker(logger, 5, 5*time.Minute),
))func CircuitBreakerWithHandle(logger Logger, threshold int, cooldown time.Duration, opts ...CircuitBreakerOption) (JobWrapper, *CircuitBreakerHandle)CircuitBreakerWithHandle is like CircuitBreaker but also returns a
*CircuitBreakerHandle for querying the circuit breaker's internal state.
The handle is safe for concurrent use and can be used from health checks,
dashboards, or metrics exporters.
Example:
wrapper, handle := cron.CircuitBreakerWithHandle(logger, 5, 5*time.Minute)
c := cron.New(cron.WithChain(cron.Recover(logger), wrapper))
// In a health check endpoint:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
if handle.State() == cron.CircuitOpen {
http.Error(w, "circuit open", http.StatusServiceUnavailable)
return
}
fmt.Fprintf(w, "ok (failures=%d)", handle.Failures())
})type CircuitBreakerHandle struct { /* unexported fields */ }CircuitBreakerHandle provides read-only access to circuit breaker state. All methods are safe for concurrent use.
| Method | Returns | Description |
|---|---|---|
State() |
CircuitBreakerState |
Current state (Closed, Open, HalfOpen) |
Failures() |
int64 |
Current consecutive failure count |
LastFailure() |
time.Time |
Time of last failure (zero if none) |
CooldownEnds() |
time.Time |
When cooldown expires (zero if not open) |
type CircuitBreakerState int
const (
CircuitClosed CircuitBreakerState = iota // Normal operation
CircuitOpen // Skipping execution
CircuitHalfOpen // Probing recovery
)CircuitBreakerState represents the current state of a circuit breaker.
The String() method returns "closed", "open", or "half-open".
type CircuitBreakerEvent struct {
OldState CircuitBreakerState // State before the transition
NewState CircuitBreakerState // State after the transition
Failures int64 // Current consecutive failure count
Err any // Panic value that caused the transition (nil on success)
}CircuitBreakerEvent represents a state transition in the circuit breaker,
passed to the callback configured via WithStateChangeCallback.
type CircuitBreakerOption func(*circuitBreakerConfig)CircuitBreakerOption configures optional behavior for CircuitBreaker and
CircuitBreakerWithHandle.
func WithStateChangeCallback(fn func(CircuitBreakerEvent)) CircuitBreakerOptionWithStateChangeCallback sets a callback invoked on circuit breaker state transitions (Closed→Open, Open→HalfOpen, HalfOpen→Closed, HalfOpen→Open). The callback is invoked synchronously; keep it fast.
Example with Prometheus:
cron.CircuitBreaker(logger, 5, 5*time.Minute,
cron.WithStateChangeCallback(func(e cron.CircuitBreakerEvent) {
circuitState.WithLabelValues(e.NewState.String()).Set(1)
if e.NewState == cron.CircuitOpen {
circuitTrips.Inc()
}
}),
)func TimeoutWithContext(logger Logger, timeout time.Duration, opts ...TimeoutOption) JobWrapperTimeoutWithContext wraps a job with a timeout that supports true cancellation.
Unlike Timeout, this passes a context with deadline to JobWithContext jobs,
allowing cooperative cancellation. A 5-second grace period is allowed after
context cancellation before the goroutine is abandoned.
Example:
c := cron.New(cron.WithChain(
cron.TimeoutWithContext(cron.DefaultLogger, 5*time.Minute),
))
c.AddJob("@every 1h", cron.FuncJobWithContext(func(ctx context.Context) {
select {
case <-ctx.Done():
return // Timeout - clean up
case <-time.After(1 * time.Minute):
// Done
}
}))type Job interface {
Run()
}Job is an interface for submitted cron jobs.
type FuncJob func()FuncJob is a wrapper that turns a func() into a cron.Job.
func (f FuncJob) Run()Run calls the wrapped function.
type JobWithContext interface {
Job
RunWithContext(ctx context.Context)
}JobWithContext is an optional interface for jobs that support context.Context.
If a job implements this interface, RunWithContext is called instead of Run,
allowing the job to receive cancellation signals, respect deadlines, and access
request-scoped values.
Each entry has its own per-entry context derived from the Cron's base context. The context is canceled when the entry is removed or its job is replaced.
type FuncJobWithContext func(ctx context.Context)FuncJobWithContext is a wrapper that turns a func(context.Context) into a
JobWithContext. This enables context-aware jobs using simple functions.
func (f FuncJobWithContext) Run()Run implements Job by calling RunWithContext(context.Background()).
func (f FuncJobWithContext) RunWithContext(ctx context.Context)RunWithContext implements JobWithContext.
Example:
c.AddJob("@every 1m", cron.FuncJobWithContext(func(ctx context.Context) {
for i := 0; i < 6; i++ {
if ctx.Err() != nil {
return // Entry removed or Stop() called
}
// Do a chunk of work...
time.Sleep(5 * time.Second)
}
}))type ErrorJob interface {
Job
RunE() error
}ErrorJob is an optional interface for jobs that return errors instead of panicking.
Used by RetryOnError for Go-idiomatic error-based retry.
type FuncErrorJob func() errorFuncErrorJob is a wrapper that turns a func() error into an ErrorJob.
func (f FuncErrorJob) Run()Run implements Job by calling RunE() and panicking on error.
func (f FuncErrorJob) RunE() errorRunE implements ErrorJob.
Example:
c.AddJob("@every 5m", cron.FuncErrorJob(func() error {
return callExternalAPI()
}))type NamedJob interface {
Job
Name() string
}NamedJob is an optional interface for jobs that provide a name for observability.
If implemented, the name is passed to ObservabilityHooks callbacks.
type ObservabilityHooks struct {
OnJobStart func(entryID EntryID, name string, scheduledTime time.Time)
OnJobComplete func(entryID EntryID, name string, duration time.Duration, recovered any)
OnSchedule func(entryID EntryID, name string, nextRun time.Time)
OnWorkflowComplete func(executionID string, rootID EntryID, results map[EntryID]JobResult)
}ObservabilityHooks provides callbacks for monitoring cron operations. All callbacks are optional. Hooks are called asynchronously in separate goroutines to prevent slow callbacks from blocking the scheduler.
OnWorkflowComplete is called when all jobs in a workflow execution have resolved
(success, failure, or skipped). The results map is a snapshot copy, safe to read
without synchronization.
Example with Prometheus:
hooks := cron.ObservabilityHooks{
OnJobStart: func(id cron.EntryID, name string, scheduled time.Time) {
jobsStarted.WithLabelValues(name).Inc()
},
OnJobComplete: func(id cron.EntryID, name string, dur time.Duration, recovered any) {
jobDuration.WithLabelValues(name).Observe(dur.Seconds())
if recovered != nil {
jobPanics.WithLabelValues(name).Inc()
}
},
}
c := cron.New(cron.WithObservability(hooks))type SpecAnalysis struct {
Valid bool
Error error
NextRun time.Time
Location *time.Location
Fields map[string]string
IsDescriptor bool
Interval time.Duration
Schedule Schedule
Warnings []string
}SpecAnalysis contains detailed information about a parsed cron specification.
Returned by AnalyzeSpec.
type ValidationError struct {
Message string
Field string
Value string
}ValidationError represents a cron expression validation error with optional field and value context.
type PanicError struct {
Value any
Stack []byte
}PanicError wraps a panic value with the stack trace at the point of panic.
Implements error and Unwrap(). Used by RetryWithBackoff and safeExecute.
type Logger interface {
Info(msg string, keysAndValues ...interface{})
Error(err error, msg string, keysAndValues ...interface{})
}Logger is the logging interface used by cron. Compatible with go-logr/logr.
var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))
var DiscardLogger Logger = PrintfLogger(log.New(io.Discard, "", 0))func PrintfLogger(l *log.Logger) LoggerPrintfLogger wraps a *log.Logger in the cron Logger interface.
func VerbosePrintfLogger(l *log.Logger) LoggerVerbosePrintfLogger wraps a *log.Logger with verbose output enabled.
func NewSlogLogger(l *slog.Logger) *SlogLoggerNewSlogLogger creates a Logger that delegates to *slog.Logger.
type Clock interface {
Now() time.Time
NewTimer(d time.Duration) Timer
}Clock provides an abstraction over time operations, enabling deterministic testing.
type Timer interface {
C() <-chan time.Time // Channel that receives fire time
Stop() bool // Stop the timer
Reset(d time.Duration) bool // Reset to new duration
}Timer abstracts time.Timer for testability.
type RealClock struct{}RealClock implements Clock using the real system time.
func (RealClock) Now() time.TimeNow returns the current system time.
func (RealClock) NewTimer(d time.Duration) TimerNewTimer creates a real time.Timer.
type FakeClock struct {
// contains filtered or unexported fields
}FakeClock is a Clock implementation for testing with controlled time.
func NewFakeClock(t time.Time) *FakeClockNewFakeClock creates a FakeClock initialized to the given time.
func (f *FakeClock) Now() time.TimeNow returns the fake clock's current time.
func (f *FakeClock) NewTimer(d time.Duration) TimerNewTimer creates a fake timer that fires when the clock advances past its target.
func (f *FakeClock) Set(t time.Time)Set updates the fake clock to a specific time, firing any expired timers.
func (f *FakeClock) Advance(d time.Duration)Advance moves the fake clock forward by the given duration, firing expired timers.
func (f *FakeClock) BlockUntil(n int)BlockUntil blocks until at least n timers are registered with the clock. Useful for synchronizing test setup with scheduler startup.
func (f *FakeClock) TimerCount() intTimerCount returns the number of active timers.
func ParseStandard(spec string) (Schedule, error)ParseStandard returns a Schedule for the standard 5-field cron spec.
Example:
schedule, err := cron.ParseStandard("0 6 * * ?")
if err != nil {
log.Fatal(err)
}
next := schedule.Next(time.Now())func Every(duration time.Duration) ConstantDelayScheduleEvery returns a crontab Schedule that activates once every duration. Delays of less than 1 second are rounded up to 1 second.
func ValidateSpec(spec string, options ...ParseOption) errorValidateSpec validates a cron expression without scheduling a job.
Returns nil if valid. Uses the standard parser by default; pass ParseOption
flags to customize validation (e.g., to require a seconds field).
Example:
if err := cron.ValidateSpec("0 9 * * MON-FRI"); err != nil {
return fmt.Errorf("invalid: %w", err)
}func ValidateSpecWith(spec string, parser ScheduleParser) errorValidateSpecWith validates a cron expression using any ScheduleParser implementation.
Useful with custom parsers or pre-configured Parser instances.
func ValidateSpecs(specs []string, options ...ParseOption) map[int]errorValidateSpecs validates multiple cron expressions at once. Returns a map of index to error for any invalid specs.
Example:
specs := []string{"* * * * *", "invalid", "0 9 * * MON-FRI"}
errs := cron.ValidateSpecs(specs)
for idx, err := range errs {
log.Printf("Spec %d invalid: %v", idx, err)
}func AnalyzeSpec(spec string, options ...ParseOption) SpecAnalysisAnalyzeSpec provides detailed analysis of a cron expression including validation status, next run time, parsed fields, timezone, and warnings.
Example:
result := cron.AnalyzeSpec("0 9 * * MON-FRI")
if result.Valid {
fmt.Println("Next run:", result.NextRun)
fmt.Println("Fields:", result.Fields)
fmt.Println("Warnings:", result.Warnings)
}func AnalyzeSpecWithHash(spec string, options ParseOption, hashSeed string) SpecAnalysisAnalyzeSpecWithHash analyzes a cron expression containing H hash expressions. The seed (e.g., job name) produces deterministic, distributed scheduling times.
func WorkflowExecutionID(ctx context.Context) stringWorkflowExecutionID returns the workflow execution ID from the context, or an
empty string if the job is not part of a workflow execution. Use this inside a
JobWithContext to correlate log entries or metrics with a specific workflow run.
Example:
c.AddJob("@triggered", cron.FuncJobWithContext(func(ctx context.Context) {
if execID := cron.WorkflowExecutionID(ctx); execID != "" {
log.Printf("Running as part of workflow %s", execID)
}
}), cron.WithName("transform"))func NextN(schedule Schedule, t time.Time, n int) []time.TimeNextN returns the next n execution times for the schedule, starting after t. Returns nil if schedule is nil or n <= 0.
Example:
schedule, _ := cron.ParseStandard("0 9 * * MON-FRI")
times := cron.NextN(schedule, time.Now(), 10)
for _, t := range times {
fmt.Println("Next run:", t)
}func PrevN(schedule Schedule, t time.Time, n int) []time.TimePrevN returns the previous n execution times for the schedule, before t.
Returns nil if schedule is nil, n <= 0, or schedule doesn't implement ScheduleWithPrev.
Times are returned in reverse chronological order (most recent first).
Stops early if Prev() returns zero time (no earlier execution exists).
Example:
schedule, _ := cron.ParseStandard("0 9 * * MON-FRI")
times := cron.PrevN(schedule, time.Now(), 10)
for _, t := range times {
fmt.Println("Previous run:", t)
}func Between(schedule Schedule, start, end time.Time) []time.TimeBetween returns all execution times in the range [start, end). The end time is exclusive. Returns nil if schedule is nil.
func BetweenWithLimit(schedule Schedule, start, end time.Time, limit int) []time.TimeBetweenWithLimit returns execution times in the range [start, end) up to limit. If limit is 0 or negative, no limit is applied.
func Count(schedule Schedule, start, end time.Time) intCount returns the number of executions in the range [start, end).
func CountWithLimit(schedule Schedule, start, end time.Time, limit int) intCountWithLimit counts executions in the range [start, end) up to limit.
func Matches(schedule Schedule, t time.Time) boolMatches reports whether the given time matches the schedule.
Returns false if schedule is nil or doesn't implement ScheduleWithPrev.
const MaxSpecLength = 1024MaxSpecLength is the maximum allowed length for a cron spec string.
var ErrEntryNotFound = errors.New("cron: entry not found")Returned by update methods (UpdateSchedule, UpdateScheduleByName, UpdateJob,
UpdateJobByName, UpdateEntry, UpdateEntryByName, UpdateEntryJob,
UpdateEntryJobByName), pause/resume methods (PauseEntry, PauseEntryByName,
ResumeEntry, ResumeEntryByName), and trigger methods (TriggerEntry,
TriggerEntryByName) when the specified entry does not exist.
var ErrNilJob = errors.New("cron: job must not be nil; use UpdateSchedule to update only the schedule")Returned by UpdateEntry, UpdateEntryByName, UpdateEntryJob, and
UpdateEntryJobByName when a nil job is passed.
var ErrNameRequired = errors.New("cron: UpsertJob requires WithName option")Returned by UpsertJob when no WithName option is provided.
var ErrMaxEntriesReached = errors.New("cron: max entries limit reached")Returned by AddFunc, AddJob, and ScheduleJob when the WithMaxEntries limit
has been reached.
var ErrEntryPaused = errors.New("cron: entry is paused")Returned by TriggerEntry and TriggerEntryByName when attempting to trigger
a paused entry. Resume the entry first.
var ErrNotRunning = errors.New("cron: scheduler is not running")Returned by TriggerEntry and TriggerEntryByName when the scheduler is not
running. Start the scheduler first.
var ErrDuplicateName = errors.New("cron: duplicate entry name")Returned when adding an entry with a name that already exists.
var ErrEmptySpec = &ValidationError{Message: "empty spec string"}Set as the Error field on the SpecAnalysis result returned by AnalyzeSpec
and AnalyzeSpecWithHash when an empty spec string is provided.
var ErrCycleDetected = errors.New("cron: dependency would create a cycle")Returned by AddDependency, AddDependencyByName, and AddWorkflow when the
new dependency edge would create a cycle in the DAG.
var ErrInvalidCondition = errors.New("cron: invalid trigger condition")Returned by AddDependency and AddDependencyByName when the provided
TriggerCondition is not a valid known value.
var ErrMultipleFinalSteps = errors.New("cron: workflow has multiple final steps")Returned by AddWorkflow when more than one step in the workflow is marked Final.
var ErrUnknownStep = errors.New("cron: workflow step references unknown parent")Returned by AddWorkflow when a step's After references a parent name that
does not exist in the workflow.
var ErrEmptyWorkflow = errors.New("cron: workflow has no steps")Returned by AddWorkflow when the workflow has no steps.
Generated: 2026-02-14