or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration-options.mdcore-scheduler.mdindex.mdjob-management.mdjob-wrappers.mdlogging.mdschedule-parsing.mdschedule-types.md
tile.json

job-management.mddocs/

Job Management

Add, remove, and inspect scheduled jobs in the cron scheduler.

Adding Jobs

Adding Functions

// 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.
// Returns an error if the spec cannot be parsed.
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)

Parameters:

  • spec string - Cron specification string (e.g., "0 30 * * * *", "@hourly", "@every 5m")
  • cmd func() - Function to execute on schedule

Returns:

  • EntryID - Unique identifier for the scheduled job
  • error - Error if spec is invalid

Usage:

c := cron.New()

// Standard cron spec (5 fields: minute hour day month weekday)
id1, err := c.AddFunc("30 * * * *", func() {
    fmt.Println("Every hour on the half hour")
})
if err != nil {
    log.Fatal(err)
}

// Descriptor syntax
id2, _ := c.AddFunc("@hourly", func() {
    fmt.Println("Every hour")
})

// Interval syntax
id3, _ := c.AddFunc("@every 1h30m", func() {
    fmt.Println("Every 90 minutes")
})

// With timezone prefix
id4, _ := c.AddFunc("CRON_TZ=America/New_York 0 6 * * ?", func() {
    fmt.Println("6am in New York")
})

c.Start()

Adding Job Objects

// 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.
// Returns an error if the spec cannot be parsed.
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error)

Type Definitions:

type Job interface {
    Run()
}

type EntryID int

Usage:

// Define a job type
type MyJob struct {
    Name string
}

func (j MyJob) Run() {
    fmt.Printf("Running job: %s\n", j.Name)
}

// Add the job
c := cron.New()
job := MyJob{Name: "DataProcessor"}
id, err := c.AddJob("@daily", job)
if err != nil {
    log.Fatal(err)
}

Scheduling with Pre-Parsed Schedule

// Schedule adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
// Returns the entry ID immediately.
func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID

Type Definitions:

type Schedule interface {
    // Next returns the next activation time, later than the given time
    Next(time.Time) time.Time
}

Usage:

// Parse schedule separately
schedule, err := cron.ParseStandard("0 0 * * *")
if err != nil {
    log.Fatal(err)
}

// Define job
job := MyJob{Name: "Report"}

// Schedule it
c := cron.New()
id := c.Schedule(schedule, job)

Removing Jobs

// Remove an entry from being run in the future.
// If the job is currently running, it will complete.
func (c *Cron) Remove(id EntryID)

Usage:

c := cron.New()

// Add a job
id, _ := c.AddFunc("@every 5s", func() {
    fmt.Println("Temporary job")
})

c.Start()

// Later: remove it
time.Sleep(30 * time.Second)
c.Remove(id)
fmt.Println("Job removed")

Inspecting Entries

Get All Entries

// Entries returns a snapshot of the cron entries.
// The returned slice contains copies of entries, not references.
func (c *Cron) Entries() []Entry

Type Definitions:

type Entry struct {
    // ID is the cron-assigned ID of this entry
    ID EntryID

    // Schedule on which this job should be run
    Schedule Schedule

    // Next time the job will run, or zero time if Cron hasn't started
    // or this entry's schedule is unsatisfiable
    Next time.Time

    // Prev is the last time this job was run, or zero time if never
    Prev time.Time

    // WrappedJob is the thing to run when the Schedule is activated
    WrappedJob Job

    // Job is the original job submitted to cron
    // Kept around so user code can access it later
    Job Job
}

// Valid returns true if this is not the zero entry
func (e Entry) Valid() bool

Usage:

c := cron.New()
c.AddFunc("@hourly", func() { fmt.Println("Job 1") })
c.AddFunc("@daily", func() { fmt.Println("Job 2") })
c.Start()

// Inspect all entries
entries := c.Entries()
for _, entry := range entries {
    fmt.Printf("Job ID %d: Next run at %v, Last run at %v\n",
        entry.ID, entry.Next, entry.Prev)
}

Get Single Entry

// Entry returns a snapshot of the given entry, or nil if it couldn't be found.
// Returns a zero-value Entry if not found (check with Valid()).
func (c *Cron) Entry(id EntryID) Entry

Usage:

c := cron.New()
id, _ := c.AddFunc("@hourly", func() { fmt.Println("Job") })
c.Start()

// Get specific entry
entry := c.Entry(id)
if entry.Valid() {
    fmt.Printf("Next run: %v\n", entry.Next)
    fmt.Printf("Previous run: %v\n", entry.Prev)
} else {
    fmt.Println("Entry not found")
}

Getting Timezone

// Location gets the time zone location.
// Returns the location used for interpreting schedules.
func (c *Cron) Location() *time.Location

Usage:

nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))

loc := c.Location()
fmt.Println("Scheduler timezone:", loc)

FuncJob Helper Type

The AddFunc method internally wraps functions using the FuncJob type. You can use this type directly if needed.

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

// Run implements the Job interface
func (f FuncJob) Run()

Usage:

// These are equivalent:
c.AddFunc("@hourly", func() { fmt.Println("Hello") })

// Or explicitly:
var job cron.FuncJob = func() { fmt.Println("Hello") }
c.AddJob("@hourly", job)

Dynamic Job Management Example

package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/robfig/cron/v3"
)

type JobManager struct {
    cron    *cron.Cron
    jobs    map[string]cron.EntryID
    mu      sync.Mutex
}

func NewJobManager() *JobManager {
    return &JobManager{
        cron: cron.New(),
        jobs: make(map[string]cron.EntryID),
    }
}

func (jm *JobManager) Start() {
    jm.cron.Start()
}

func (jm *JobManager) Stop() {
    jm.cron.Stop()
}

func (jm *JobManager) AddJob(name string, spec string, fn func()) error {
    jm.mu.Lock()
    defer jm.mu.Unlock()

    // Remove existing job with same name if present
    if id, exists := jm.jobs[name]; exists {
        jm.cron.Remove(id)
    }

    // Add new job
    id, err := jm.cron.AddFunc(spec, fn)
    if err != nil {
        return err
    }

    jm.jobs[name] = id
    return nil
}

func (jm *JobManager) RemoveJob(name string) {
    jm.mu.Lock()
    defer jm.mu.Unlock()

    if id, exists := jm.jobs[name]; exists {
        jm.cron.Remove(id)
        delete(jm.jobs, name)
    }
}

func (jm *JobManager) ListJobs() []string {
    jm.mu.Lock()
    defer jm.mu.Unlock()

    names := make([]string, 0, len(jm.jobs))
    for name := range jm.jobs {
        names = append(names, name)
    }
    return names
}

func main() {
    jm := NewJobManager()
    jm.Start()

    // Add jobs dynamically
    jm.AddJob("report", "@daily", func() {
        fmt.Println("Running daily report")
    })

    jm.AddJob("backup", "@every 6h", func() {
        fmt.Println("Running backup")
    })

    // List all jobs
    fmt.Println("Active jobs:", jm.ListJobs())

    // Update a job (replaces existing)
    jm.AddJob("report", "@hourly", func() {
        fmt.Println("Running hourly report (updated)")
    })

    // Remove a job
    jm.RemoveJob("backup")

    // Clean shutdown
    defer jm.Stop()

    select {}
}

Job Execution Behavior

Concurrency

  • Jobs are executed in separate goroutines
  • Multiple jobs can run simultaneously
  • No built-in job serialization (use job wrappers for this)

Error Handling

  • Jobs that panic are handled by the configured job wrappers
  • By default, panics are recovered and logged (via the default Chain)
  • Return values from job functions are ignored (use shared state or channels to communicate results)

Timing

  • Jobs are scheduled using the Next() method of their Schedule
  • After a job runs, its next run time is immediately calculated
  • If a job's execution time exceeds its interval, it doesn't "catch up" - the next run is scheduled from the current time

Late Start

If a job is added while the scheduler is running, it's scheduled immediately based on the current time, not from when the scheduler started.