go-cron API Reference

June 2, 2026 · View on GitHub

Complete API documentation for all exported types, functions, and methods

Table of Contents


Package cron

import "github.com/netresearch/go-cron"

Package cron implements a cron spec parser and job runner.


Types

Cron

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

func New(opts ...Option) *Cron

New 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 (*Cron) AddFunc

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 (*Cron) AddJob

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 (*Cron) Schedule

func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID

Schedule 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 (*Cron) UpdateJob

func (c *Cron) UpdateJob(id EntryID, spec string) error

UpdateJob 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 (*Cron) UpdateJobByName

func (c *Cron) UpdateJobByName(name, spec string) error

UpdateJobByName 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 (*Cron) UpdateSchedule

func (c *Cron) UpdateSchedule(id EntryID, schedule Schedule) error

UpdateSchedule 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 Next from clock.Now(); Prev is 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 (*Cron) UpdateScheduleByName

func (c *Cron) UpdateScheduleByName(name string, schedule Schedule) error

UpdateScheduleByName 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 (*Cron) UpdateEntry

func (c *Cron) UpdateEntry(id EntryID, schedule Schedule, job Job) error

UpdateEntry 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 (*Cron) UpdateEntryByName

func (c *Cron) UpdateEntryByName(name string, schedule Schedule, job Job) error

UpdateEntryByName 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 (*Cron) UpdateEntryJob

func (c *Cron) UpdateEntryJob(id EntryID, spec string, job Job) error

UpdateEntryJob 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 (*Cron) UpdateEntryJobByName

func (c *Cron) UpdateEntryJobByName(name, spec string, job Job) error

UpdateEntryJobByName 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 (*Cron) UpsertJob

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 (*Cron) DrainAndUpsertJob

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 (*Cron) WaitForJob

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 (*Cron) WaitForJobByName

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 DrainAndUpsertJob for a windowless reschedule.

func (*Cron) IsJobRunning

func (c *Cron) IsJobRunning(id EntryID) bool

IsJobRunning reports whether the entry with the given ID has any invocations currently in flight. Returns false for non-existent entries.

func (*Cron) IsJobRunningByName

func (c *Cron) IsJobRunningByName(name string) bool

IsJobRunningByName 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 (*Cron) PauseEntry

func (c *Cron) PauseEntry(id EntryID) error

PauseEntry 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 (*Cron) PauseEntryByName

func (c *Cron) PauseEntryByName(name string) error

PauseEntryByName 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 (*Cron) ResumeEntry

func (c *Cron) ResumeEntry(id EntryID) error

ResumeEntry 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 (*Cron) ResumeEntryByName

func (c *Cron) ResumeEntryByName(name string) error

ResumeEntryByName 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 (*Cron) IsEntryPaused

func (c *Cron) IsEntryPaused(id EntryID) bool

IsEntryPaused reports whether the entry with the given ID is currently paused. Returns false if the entry does not exist.

func (*Cron) IsEntryPausedByName

func (c *Cron) IsEntryPausedByName(name string) bool

IsEntryPausedByName 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 (*Cron) AddDependency

func (c *Cron) AddDependency(child, parent EntryID, condition TriggerCondition) error

AddDependency 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 (*Cron) AddDependencyByName

func (c *Cron) AddDependencyByName(child, parent string, condition TriggerCondition) error

AddDependencyByName is the name-based variant of AddDependency. Returns ErrEntryNotFound if either name does not exist.

Example:

c.AddDependencyByName("transform", "extract", cron.OnSuccess)

func (*Cron) RemoveDependency

func (c *Cron) RemoveDependency(child, parent EntryID) error

RemoveDependency removes a dependency edge between child and parent.

func (*Cron) RemoveDependencyByName

func (c *Cron) RemoveDependencyByName(child, parent string) error

RemoveDependencyByName is the name-based variant of RemoveDependency. Returns ErrEntryNotFound if either name does not exist.

func (*Cron) Dependencies

func (c *Cron) Dependencies(id EntryID) []Dependency

Dependencies returns a copy of the dependency edges for an entry. Returns nil if the entry has no dependencies.

func (*Cron) DependenciesByName

func (c *Cron) DependenciesByName(name string) []Dependency

DependenciesByName is the name-based variant of Dependencies. Returns nil if no entry has the given name or the entry has no dependencies.

func (*Cron) AddWorkflow

func (c *Cron) AddWorkflow(w *Workflow) error

AddWorkflow 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:

  • ErrEmptyWorkflow if the workflow has no steps
  • ErrMultipleFinalSteps if more than one step is marked Final
  • ErrUnknownStep if a step references an unknown parent via After
  • ErrCycleDetected if the step dependencies form a cycle
  • ErrDuplicateName if 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 (*Cron) WorkflowStatus

func (c *Cron) WorkflowStatus(executionID string) *WorkflowExecution

WorkflowStatus 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 (*Cron) ActiveWorkflows

func (c *Cron) ActiveWorkflows() []WorkflowExecution

ActiveWorkflows 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 (*Cron) TriggerEntry

func (c *Cron) TriggerEntry(id EntryID) error

TriggerEntry 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 demand

func (*Cron) TriggerEntryByName

func (c *Cron) TriggerEntryByName(name string) error

TriggerEntryByName 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 (*Cron) ScheduleJob

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 (*Cron) AddOnceFunc

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 (*Cron) AddOnceJob

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 (*Cron) ScheduleOnceJob

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 (*Cron) ValidateSpec

func (c *Cron) ValidateSpec(spec string) error

ValidateSpec 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 (*Cron) Entries

func (c *Cron) Entries() []Entry

Entries 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 (*Cron) Entry

func (c *Cron) Entry(id EntryID) Entry

Entry 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 (*Cron) EntryByName

func (c *Cron) EntryByName(name string) Entry

EntryByName 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 (*Cron) EntriesByTag

func (c *Cron) EntriesByTag(tag string) []Entry

EntriesByTag 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 entries

func (*Cron) Remove

func (c *Cron) Remove(id EntryID)

Remove an entry from being run in the future. Cancels the entry's per-entry context.

func (*Cron) RemoveByName

func (c *Cron) RemoveByName(name string) bool

RemoveByName removes the entry with the given name. Returns true if an entry was removed, false if no entry had that name.

func (*Cron) RemoveByTag

func (c *Cron) RemoveByTag(tag string) int

RemoveByTag removes all entries that have the given tag. Returns the number of entries removed.

func (*Cron) IsRunning

func (c *Cron) IsRunning() bool

IsRunning returns true if the cron scheduler is currently running.

func (*Cron) Start

func (c *Cron) Start()

Start the cron scheduler in its own goroutine, or no-op if already started.

func (*Cron) Run

func (c *Cron) Run()

Run the cron scheduler in the current goroutine (blocking), or no-op if already running.

func (*Cron) Stop

func (c *Cron) Stop() context.Context

Stop 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 finish

func (*Cron) StopAndWait

func (c *Cron) StopAndWait()

StopAndWait stops the cron scheduler and blocks until all running jobs complete. Equivalent to <-c.Stop().Done().

func (*Cron) StopWithTimeout

func (c *Cron) StopWithTimeout(timeout time.Duration) bool

StopWithTimeout 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 (*Cron) Location

func (c *Cron) Location() *time.Location

Location gets the time zone location.


Entry

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
}

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 (Entry) Valid

func (e Entry) Valid() bool

Valid returns true if this is not the zero entry.

func (Entry) Run

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.


EntryID

type EntryID uint64

EntryID identifies an entry within a Cron instance. Using uint64 prevents overflow and ID collisions on all platforms.


MissedPolicy

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.


JobOption

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

func WithName(name string) JobOption

WithName 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

func WithPrev(t time.Time) JobOption

WithPrev 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

func WithMissedPolicy(policy MissedPolicy) JobOption

WithMissedPolicy sets the policy for handling missed job executions. See MissedPolicy for available options.

func WithMissedGracePeriod

func WithMissedGracePeriod(d time.Duration) JobOption

WithMissedGracePeriod 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

func WithTags(tags ...string) JobOption

WithTags 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")

Option

type Option func(*Cron)

Option represents a modification to the default behavior of a Cron.

func WithLocation

func WithLocation(loc *time.Location) Option

WithLocation overrides the timezone of the cron instance.

func WithSeconds

func WithSeconds() Option

WithSeconds overrides the parser to include a seconds field as the first field. Equivalent to Quartz scheduler format.

func WithParser

func WithParser(p ScheduleParser) Option

WithParser overrides the parser used for interpreting job schedules.

func WithChain

func WithChain(wrappers ...JobWrapper) Option

WithChain specifies Job wrappers to apply to all jobs added to this cron.

func WithLogger

func WithLogger(logger Logger) Option

WithLogger uses the provided logger.

func WithClock

func WithClock(clock Clock) Option

WithClock 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 overriding

func WithContext

func WithContext(ctx context.Context) Option

WithContext 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

func WithMaxEntries(maxEntries int) Option

WithMaxEntries limits the maximum number of entries. When the limit is reached, AddFunc and AddJob return ErrMaxEntriesReached. A limit of 0 means unlimited.

func WithObservability

func WithObservability(hooks ObservabilityHooks) Option

WithObservability configures observability hooks for monitoring cron operations.

func WithSecondOptional

func WithSecondOptional() Option

WithSecondOptional overrides the parser to accept an optional seconds field. Expressions can have either 5 fields (standard) or 6 fields (with seconds).

func WithMinEveryInterval

func WithMinEveryInterval(d time.Duration) Option

WithMinEveryInterval configures the minimum interval allowed for @every expressions. Default is 1 second. Set to 0 for sub-second intervals (useful for testing).

func WithMaxSearchYears

func WithMaxSearchYears(years int) Option

WithMaxSearchYears configures how far into the future schedule matching searches before giving up. Default is 5 years.

func WithRunImmediately

func WithRunImmediately() JobOption

WithRunImmediately causes the job to run immediately upon registration, then follow the normal schedule thereafter.

func WithRunOnce

func WithRunOnce() JobOption

WithRunOnce causes the job to be automatically removed after its first execution.

func WithPaused

func WithPaused() JobOption

WithPaused 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

func WithCapacity(n int) Option

WithCapacity 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:

  • entryIndex map with capacity n (O(1) lookup by ID)
  • nameIndex map with capacity n (O(1) lookup by name)
  • entries heap 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

func WithWorkflowRetention(n int) Option

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

TriggerCondition

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 (TriggerCondition) String

func (c TriggerCondition) String() string

String returns the human-readable name ("OnSuccess", "OnFailure", "OnSkipped", "OnComplete").

func (TriggerCondition) Valid

func (c TriggerCondition) Valid() bool

Valid reports whether c is a known trigger condition.

func (TriggerCondition) Matches

func (c TriggerCondition) Matches(result JobResult) bool

Matches reports whether the given parent result satisfies this condition. OnComplete matches any terminal result; the others match their specific JobResult.


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 (JobResult) String

func (r JobResult) String() string

String returns the human-readable name ("Pending", "Success", "Failure", "Skipped").

func (JobResult) IsTerminal

func (r JobResult) IsTerminal() bool

IsTerminal reports whether the result represents a final state. Returns true for ResultSuccess, ResultFailure, and ResultSkipped; false for ResultPending.


Dependency

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.


WorkflowExecution

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 (*WorkflowExecution) IsComplete

func (we *WorkflowExecution) IsComplete() bool

IsComplete reports whether every job in the execution has reached a terminal state.


Workflow

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

func NewWorkflow(name string) *Workflow

NewWorkflow 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 (*Workflow) Step

func (w *Workflow) Step(name, spec string, job Job) *WorkflowStep

Step adds a named step to the workflow with the given schedule spec and Job.

func (*Workflow) StepFunc

func (w *Workflow) StepFunc(name, spec string, fn func()) *WorkflowStep

StepFunc adds a named step with a plain function as its job.

func (*WorkflowStep) After

func (s *WorkflowStep) After(parentName string, condition TriggerCondition) *WorkflowStep

After 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 succeed

func (*WorkflowStep) Final

func (s *WorkflowStep) Final() *WorkflowStep

Final 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.


Schedule

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.


ScheduleWithPrev

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

ScheduleParser

type ScheduleParser interface {
    Parse(spec string) (Schedule, error)
}

ScheduleParser is an interface for schedule spec parsers.


SpecSchedule

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 (*SpecSchedule) Next

func (s *SpecSchedule) Next(t time.Time) time.Time

Next returns the next activation time after the given time. Returns zero time if no valid time exists.


ConstantDelaySchedule

type ConstantDelaySchedule struct {
    Delay time.Duration
}

ConstantDelaySchedule represents a simple recurring duty cycle.

func Every

func Every(duration time.Duration) ConstantDelaySchedule

Every 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 (ConstantDelaySchedule) Next

func (s ConstantDelaySchedule) Next(t time.Time) time.Time

Next returns the next activation time after t.


TriggeredSchedule

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

func (TriggeredSchedule) Next(time.Time) time.Time

Next always returns the zero time.

func (TriggeredSchedule) Prev

func (TriggeredSchedule) Prev(time.Time) time.Time

Prev always returns the zero time.

func IsTriggered

func IsTriggered(s Schedule) bool

IsTriggered reports whether the given schedule is a TriggeredSchedule.

Example:

if cron.IsTriggered(entry.Schedule) {
    fmt.Println("This entry only runs when triggered manually")
}

Parser

type Parser struct {
    // contains filtered or unexported fields
}

Parser parses cron specs into Schedule objects.

func NewParser

func NewParser(options ParseOption) Parser

NewParser 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 (Parser) Parse

func (p Parser) Parse(spec string) (Schedule, error)

Parse returns a Schedule from a cron spec or error if invalid.


ParseOption

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.


Chain

type Chain struct {
    // contains filtered or unexported fields
}

Chain is a sequence of JobWrappers.

func NewChain

func NewChain(c ...JobWrapper) Chain

NewChain returns a Chain of the given JobWrappers.

func (Chain) Then

func (c Chain) Then(j Job) Job

Then applies all wrappers to the given job and returns the wrapped job.


JobWrapper

type JobWrapper func(Job) Job

JobWrapper 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

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

func Recover(logger Logger, opts ...RecoverOption) JobWrapper

Recover 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

func SkipIfStillRunning(logger Logger) JobWrapper

SkipIfStillRunning skips a job invocation if the previous one is still running. Propagates context to context-aware inner jobs.

func DelayIfStillRunning

func DelayIfStillRunning(logger Logger) JobWrapper

DelayIfStillRunning delays a job invocation until the previous one completes. Propagates context to context-aware inner jobs.

func Timeout

func Timeout(logger Logger, timeout time.Duration, opts ...TimeoutOption) JobWrapper

Timeout 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

func Jitter(maxJitter time.Duration) JobWrapper

Jitter adds a random delay before job execution to prevent thundering herd. Propagates context to context-aware inner jobs.

func JitterWithLogger

func JitterWithLogger(logger Logger, maxJitter time.Duration) JobWrapper

JitterWithLogger is like Jitter but logs the applied delay. Propagates context to context-aware inner jobs.

func MaxConcurrent

func MaxConcurrent(n int) JobWrapper

MaxConcurrent 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

func MaxConcurrentSkip(logger Logger, n int) JobWrapper

MaxConcurrentSkip 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

func RetryWithBackoff(logger Logger, maxRetries int, initialDelay, maxDelay time.Duration, multiplier float64, opts ...RetryOption) JobWrapper

RetryWithBackoff wraps a job to retry on panic with exponential backoff.

  • maxRetries: 0 = no retries, >0 = retry up to N times, -1 = unlimited
  • initialDelay: First retry delay
  • maxDelay: Maximum delay cap
  • multiplier: Delay multiplier per retry (typically 2.0)
  • opts: Optional RetryOption values (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

func RetryOnError(logger Logger, maxRetries int, initialDelay, maxDelay time.Duration, multiplier float64, opts ...RetryOption) JobWrapper

RetryOnError 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

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

type RetryOption func(*retryConfig)

RetryOption configures optional behavior for RetryWithBackoff and RetryOnError.

func WithRetryCallback

func WithRetryCallback(fn func(RetryAttempt)) RetryOption

WithRetryCallback 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

func CircuitBreaker(logger Logger, threshold int, cooldown time.Duration, opts ...CircuitBreakerOption) JobWrapper

CircuitBreaker wraps a job to stop execution after consecutive failures.

  • Closed: Normal execution. Failures increment counter.
  • Open: Execution skipped for cooldown duration after threshold failures.
  • Half-Open: After cooldown, one execution attempted. Success closes, failure reopens.
  • opts: Optional CircuitBreakerOption values (e.g., WithStateChangeCallback)

Example:

c := cron.New(cron.WithChain(
    cron.Recover(logger),
    cron.CircuitBreaker(logger, 5, 5*time.Minute),
))

func CircuitBreakerWithHandle

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

type CircuitBreakerHandle struct { /* unexported fields */ }

CircuitBreakerHandle provides read-only access to circuit breaker state. All methods are safe for concurrent use.

MethodReturnsDescription
State()CircuitBreakerStateCurrent state (Closed, Open, HalfOpen)
Failures()int64Current consecutive failure count
LastFailure()time.TimeTime of last failure (zero if none)
CooldownEnds()time.TimeWhen cooldown expires (zero if not open)

type CircuitBreakerState

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

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

type CircuitBreakerOption func(*circuitBreakerConfig)

CircuitBreakerOption configures optional behavior for CircuitBreaker and CircuitBreakerWithHandle.

func WithStateChangeCallback

func WithStateChangeCallback(fn func(CircuitBreakerEvent)) CircuitBreakerOption

WithStateChangeCallback 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

func TimeoutWithContext(logger Logger, timeout time.Duration, opts ...TimeoutOption) JobWrapper

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

Job

type Job interface {
    Run()
}

Job is an interface for submitted cron jobs.


FuncJob

type FuncJob func()

FuncJob is a wrapper that turns a func() into a cron.Job.

func (FuncJob) Run

func (f FuncJob) Run()

Run calls the wrapped function.


JobWithContext

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.


FuncJobWithContext

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 (FuncJobWithContext) Run

func (f FuncJobWithContext) Run()

Run implements Job by calling RunWithContext(context.Background()).

func (FuncJobWithContext) RunWithContext

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

ErrorJob

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.


FuncErrorJob

type FuncErrorJob func() error

FuncErrorJob is a wrapper that turns a func() error into an ErrorJob.

func (FuncErrorJob) Run

func (f FuncErrorJob) Run()

Run implements Job by calling RunE() and panicking on error.

func (FuncErrorJob) RunE

func (f FuncErrorJob) RunE() error

RunE implements ErrorJob.

Example:

c.AddJob("@every 5m", cron.FuncErrorJob(func() error {
    return callExternalAPI()
}))

NamedJob

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.


ObservabilityHooks

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

SpecAnalysis

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.


ValidationError

type ValidationError struct {
    Message string
    Field   string
    Value   string
}

ValidationError represents a cron expression validation error with optional field and value context.


PanicError

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.


Logger

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.

Variables

var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))
var DiscardLogger Logger = PrintfLogger(log.New(io.Discard, "", 0))

func PrintfLogger

func PrintfLogger(l *log.Logger) Logger

PrintfLogger wraps a *log.Logger in the cron Logger interface.

func VerbosePrintfLogger

func VerbosePrintfLogger(l *log.Logger) Logger

VerbosePrintfLogger wraps a *log.Logger with verbose output enabled.

func NewSlogLogger

func NewSlogLogger(l *slog.Logger) *SlogLogger

NewSlogLogger creates a Logger that delegates to *slog.Logger.


Clock

type Clock interface {
    Now() time.Time
    NewTimer(d time.Duration) Timer
}

Clock provides an abstraction over time operations, enabling deterministic testing.


Timer

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.


RealClock

type RealClock struct{}

RealClock implements Clock using the real system time.

func (RealClock) Now

func (RealClock) Now() time.Time

Now returns the current system time.

func (RealClock) NewTimer

func (RealClock) NewTimer(d time.Duration) Timer

NewTimer creates a real time.Timer.


FakeClock

type FakeClock struct {
    // contains filtered or unexported fields
}

FakeClock is a Clock implementation for testing with controlled time.

func NewFakeClock

func NewFakeClock(t time.Time) *FakeClock

NewFakeClock creates a FakeClock initialized to the given time.

func (*FakeClock) Now

func (f *FakeClock) Now() time.Time

Now returns the fake clock's current time.

func (*FakeClock) NewTimer

func (f *FakeClock) NewTimer(d time.Duration) Timer

NewTimer creates a fake timer that fires when the clock advances past its target.

func (*FakeClock) Set

func (f *FakeClock) Set(t time.Time)

Set updates the fake clock to a specific time, firing any expired timers.

func (*FakeClock) Advance

func (f *FakeClock) Advance(d time.Duration)

Advance moves the fake clock forward by the given duration, firing expired timers.

func (*FakeClock) BlockUntil

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 (*FakeClock) TimerCount

func (f *FakeClock) TimerCount() int

TimerCount returns the number of active timers.


Functions

ParseStandard

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

Every

func Every(duration time.Duration) ConstantDelaySchedule

Every returns a crontab Schedule that activates once every duration. Delays of less than 1 second are rounded up to 1 second.

ValidateSpec

func ValidateSpec(spec string, options ...ParseOption) error

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

ValidateSpecWith

func ValidateSpecWith(spec string, parser ScheduleParser) error

ValidateSpecWith validates a cron expression using any ScheduleParser implementation. Useful with custom parsers or pre-configured Parser instances.

ValidateSpecs

func ValidateSpecs(specs []string, options ...ParseOption) map[int]error

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

AnalyzeSpec

func AnalyzeSpec(spec string, options ...ParseOption) SpecAnalysis

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

AnalyzeSpecWithHash

func AnalyzeSpecWithHash(spec string, options ParseOption, hashSeed string) SpecAnalysis

AnalyzeSpecWithHash analyzes a cron expression containing H hash expressions. The seed (e.g., job name) produces deterministic, distributed scheduling times.

WorkflowExecutionID

func WorkflowExecutionID(ctx context.Context) string

WorkflowExecutionID 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"))

Schedule Introspection

NextN

func NextN(schedule Schedule, t time.Time, n int) []time.Time

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

PrevN

func PrevN(schedule Schedule, t time.Time, n int) []time.Time

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

Between

func Between(schedule Schedule, start, end time.Time) []time.Time

Between returns all execution times in the range [start, end). The end time is exclusive. Returns nil if schedule is nil.

BetweenWithLimit

func BetweenWithLimit(schedule Schedule, start, end time.Time, limit int) []time.Time

BetweenWithLimit returns execution times in the range [start, end) up to limit. If limit is 0 or negative, no limit is applied.

Count

func Count(schedule Schedule, start, end time.Time) int

Count returns the number of executions in the range [start, end).

CountWithLimit

func CountWithLimit(schedule Schedule, start, end time.Time, limit int) int

CountWithLimit counts executions in the range [start, end) up to limit.

Matches

func Matches(schedule Schedule, t time.Time) bool

Matches reports whether the given time matches the schedule. Returns false if schedule is nil or doesn't implement ScheduleWithPrev.


Constants

MaxSpecLength

const MaxSpecLength = 1024

MaxSpecLength is the maximum allowed length for a cron spec string.


Errors

ErrEntryNotFound

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.

ErrNilJob

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.

ErrNameRequired

var ErrNameRequired = errors.New("cron: UpsertJob requires WithName option")

Returned by UpsertJob when no WithName option is provided.

ErrMaxEntriesReached

var ErrMaxEntriesReached = errors.New("cron: max entries limit reached")

Returned by AddFunc, AddJob, and ScheduleJob when the WithMaxEntries limit has been reached.

ErrEntryPaused

var ErrEntryPaused = errors.New("cron: entry is paused")

Returned by TriggerEntry and TriggerEntryByName when attempting to trigger a paused entry. Resume the entry first.

ErrNotRunning

var ErrNotRunning = errors.New("cron: scheduler is not running")

Returned by TriggerEntry and TriggerEntryByName when the scheduler is not running. Start the scheduler first.

ErrDuplicateName

var ErrDuplicateName = errors.New("cron: duplicate entry name")

Returned when adding an entry with a name that already exists.

ErrEmptySpec

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.

ErrCycleDetected

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.

ErrInvalidCondition

var ErrInvalidCondition = errors.New("cron: invalid trigger condition")

Returned by AddDependency and AddDependencyByName when the provided TriggerCondition is not a valid known value.

ErrMultipleFinalSteps

var ErrMultipleFinalSteps = errors.New("cron: workflow has multiple final steps")

Returned by AddWorkflow when more than one step in the workflow is marked Final.

ErrUnknownStep

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.

ErrEmptyWorkflow

var ErrEmptyWorkflow = errors.New("cron: workflow has no steps")

Returned by AddWorkflow when the workflow has no steps.


Generated: 2026-02-14