CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com-go-co-op-gocron-v2

A Golang job scheduling library that lets you run Go functions at pre-determined intervals using cron expressions, fixed durations, daily, weekly, monthly, or one-time schedules with support for distributed deployments.

Overview
Eval results
Files

creating-jobs.mddocs/guides/jobs/

Creating Jobs

How to create jobs with NewJob, job definitions, and tasks in gocron v2.

Overview

Creating a job requires three components:

  1. Job Definition — When and how often the job runs (schedule)
  2. Task — What the job does (function)
  3. Options (optional) — Name, tags, concurrency control, etc.
j, err := s.NewJob(
    jobDefinition,  // When to run
    task,           // What to do
    options...      // How to run
)

Job Definitions

DurationJob

func DurationJob(duration time.Duration) JobDefinition

Runs every duration:

// Every 30 seconds
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(myFunc),
)

// Every 5 minutes
j, _ = s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(myFunc),
)

// Every hour
j, _ = s.NewJob(
    gocron.DurationJob(time.Hour),
    gocron.NewTask(myFunc),
)

Interval calculation: By default, intervals are measured from the scheduled start time, not completion time.

Use WithIntervalFromCompletion() to calculate from completion:

j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithIntervalFromCompletion(),
)

See Concurrency: Interval Calculation for details.

DurationRandomJob

func DurationRandomJob(min, max time.Duration) JobDefinition

Runs at a random interval between min and max:

// Between 1 and 5 minutes
j, _ := s.NewJob(
    gocron.DurationRandomJob(time.Minute, 5*time.Minute),
    gocron.NewTask(myFunc),
)

Use cases:

  • Prevent thundering herd (multiple instances)
  • Spread load over time
  • Jittered retry patterns

Randomness: New interval is chosen after each execution.

CronJob

func CronJob(cronExpression string, withSeconds bool) JobDefinition

Runs according to a cron expression:

// Every day at 9 AM
j, _ := s.NewJob(
    gocron.CronJob("0 9 * * *", false),
    gocron.NewTask(myFunc),
)

// Every minute
j, _ = s.NewJob(
    gocron.CronJob("* * * * *", false),
    gocron.NewTask(myFunc),
)

// With seconds (every 30 seconds)
j, _ = s.NewJob(
    gocron.CronJob("30 * * * * *", true),
    gocron.NewTask(myFunc),
)

Cron format:

  • 5 fields (default): minute hour day month weekday
  • 6 fields (withSeconds=true): second minute hour day month weekday

Examples:

  • 0 9 * * * — 9:00 AM every day
  • 0 9 * * 1 — 9:00 AM every Monday
  • */15 * * * * — Every 15 minutes
  • 0 9-17 * * 1-5 — Every hour from 9 AM to 5 PM, Monday-Friday

DailyJob

func DailyJob(interval uint, atTimes AtTimes) JobDefinition

Runs every interval days at specified times:

// Every day at 9 AM
j, _ := s.NewJob(
    gocron.DailyJob(
        1,
        gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

// Every day at 9 AM and 5 PM
j, _ = s.NewJob(
    gocron.DailyJob(
        1,
        gocron.NewAtTimes(
            gocron.NewAtTime(9, 0, 0),
            gocron.NewAtTime(17, 0, 0),
        ),
    ),
    gocron.NewTask(myFunc),
)

// Every 2 days at noon
j, _ = s.NewJob(
    gocron.DailyJob(2, gocron.NewAtTimes(gocron.NewAtTime(12, 0, 0))),
    gocron.NewTask(myFunc),
)

See Advanced: Time-Based Scheduling for details and edge cases.

WeeklyJob

func WeeklyJob(interval uint, daysOfTheWeek Weekdays, atTimes AtTimes) JobDefinition

Runs every interval weeks on specified days:

// Every Monday at 9 AM
j, _ := s.NewJob(
    gocron.WeeklyJob(
        1,
        gocron.NewWeekdays(time.Monday),
        gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

// Every Monday and Friday at 9 AM
j, _ = s.NewJob(
    gocron.WeeklyJob(
        1,
        gocron.NewWeekdays(time.Monday, time.Friday),
        gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

// Every 2 weeks on Friday at 5 PM
j, _ = s.NewJob(
    gocron.WeeklyJob(
        2,
        gocron.NewWeekdays(time.Friday),
        gocron.NewAtTimes(gocron.NewAtTime(17, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

Week convention: Week starts on Sunday (Go's time.Weekday).

See Advanced: Time-Based Scheduling for details.

MonthlyJob

func MonthlyJob(interval uint, daysOfTheMonth DaysOfTheMonth, atTimes AtTimes) JobDefinition

Runs every interval months on specified days:

// On the 1st of every month at noon
j, _ := s.NewJob(
    gocron.MonthlyJob(
        1,
        gocron.NewDaysOfTheMonth(1),
        gocron.NewAtTimes(gocron.NewAtTime(12, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

// On the last day of every month
j, _ = s.NewJob(
    gocron.MonthlyJob(
        1,
        gocron.NewDaysOfTheMonth(-1),
        gocron.NewAtTimes(gocron.NewAtTime(0, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

// On the 1st and 15th at noon
j, _ = s.NewJob(
    gocron.MonthlyJob(
        1,
        gocron.NewDaysOfTheMonth(1, 15),
        gocron.NewAtTimes(gocron.NewAtTime(12, 0, 0)),
    ),
    gocron.NewTask(myFunc),
)

Day numbering:

  • Positive (1-31): Count from start
  • Negative (-1 to -31): Count from end
  • -1 = last day, -2 = second-to-last, etc.

See Advanced: Time-Based Scheduling for details and edge cases.

OneTimeJob

func OneTimeJob(startAt OneTimeJobStartAtOption) JobDefinition

Runs once, then automatically removed:

// Run immediately
j, _ := s.NewJob(
    gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
    gocron.NewTask(myFunc),
)

// Run in 1 hour
futureTime := time.Now().Add(time.Hour)
j, _ = s.NewJob(
    gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(futureTime)),
    gocron.NewTask(myFunc),
)

// Run at multiple times
j, _ = s.NewJob(
    gocron.OneTimeJob(gocron.OneTimeJobStartDateTimes(
        time.Now().Add(1*time.Hour),
        time.Now().Add(2*time.Hour),
        time.Now().Add(3*time.Hour),
    )),
    gocron.NewTask(myFunc),
)

See Advanced: Time-Based Scheduling for details.

Tasks

NewTask

func NewTask(function any, parameters ...any) Task

Wraps a function and its parameters:

// Simple function
task := gocron.NewTask(func() {
    fmt.Println("Hello!")
})

// Function with parameters
task = gocron.NewTask(
    func(name string, count int) {
        fmt.Printf("Hello %s, count: %d\n", name, count)
    },
    "Alice",
    42,
)

// Function with return value (return value ignored)
task = gocron.NewTask(func() error {
    return doWork()
})

Parameter passing: Parameters are passed after the function and must match the function signature.

Context Injection

If the first parameter is context.Context, gocron injects it:

task := gocron.NewTask(func(ctx context.Context) {
    select {
    case <-ctx.Done():
        log.Println("Job cancelled")
        return
    default:
        doWork()
    }
})

Context is cancelled when:

  • Scheduler shuts down
  • Job is removed
  • Job stop time is reached
  • Parent context (from WithContext) is cancelled

Context with Parameters

task := gocron.NewTask(
    func(ctx context.Context, name string, count int) {
        select {
        case <-ctx.Done():
            return
        default:
            processData(name, count)
        }
    },
    "Alice",
    42,
)

Context is always first if present; other parameters follow.

Return Values

Return values are ignored by default. To capture errors, use event listeners:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(func() error {
        return doWork()
    }),
    gocron.WithEventListeners(
        gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
            log.Printf("Job %s failed: %v", jobName, err)
        }),
    ),
)

Common Errors

ErrNewJobTaskNil

_, err := s.NewJob(
    gocron.DurationJob(time.Minute),
    nil, // Task is nil
)
// Returns: ErrNewJobTaskNil

ErrNewJobTaskNotFunc

_, err := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask("not a function"), // Not a function
)
// Returns: ErrNewJobTaskNotFunc

ErrNewJobWrongNumberOfParameters

_, err := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(
        func(name string, count int) {},
        "Alice", // Missing second parameter
    ),
)
// Returns: ErrNewJobWrongNumberOfParameters

ErrNewJobWrongTypeOfParameters

_, err := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(
        func(name string, count int) {},
        "Alice",
        "not an int", // Wrong type
    ),
)
// Returns: ErrNewJobWrongTypeOfParameters

Advanced Task Patterns

Closures

Capture variables from outer scope:

userID := 123

task := gocron.NewTask(func() {
    // userID is captured from outer scope
    processUser(userID)
})

Warning: Be careful with loop variables. Use parameter passing instead:

// Bad: all jobs use the same userID (last value)
for _, userID := range userIDs {
    s.NewJob(
        gocron.DurationJob(time.Minute),
        gocron.NewTask(func() {
            processUser(userID) // userID is captured, may change
        }),
    )
}

// Good: pass userID as parameter
for _, userID := range userIDs {
    s.NewJob(
        gocron.DurationJob(time.Minute),
        gocron.NewTask(
            func(id int) {
                processUser(id)
            },
            userID, // Passed as parameter
        ),
    )
}

Methods

Pass methods as tasks:

type Worker struct {
    name string
}

func (w *Worker) DoWork() {
    fmt.Printf("Worker %s doing work\n", w.name)
}

w := &Worker{name: "Alice"}

task := gocron.NewTask(w.DoWork)

Generic Functions

func Process[T any](item T) {
    fmt.Printf("Processing %v\n", item)
}

// Pass type-specific function
task := gocron.NewTask(Process[string], "hello")

Best Practices

1. Use Appropriate Job Definition

Duration-based for regular intervals:

gocron.DurationJob(5*time.Minute)

Cron for complex time-based schedules:

gocron.CronJob("0 9-17 * * 1-5", false) // Business hours

Time-based for readable schedules:

gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)))

2. Always Handle Context

task := gocron.NewTask(func(ctx context.Context) {
    select {
    case <-ctx.Done():
        cleanup()
        return
    default:
        doWork()
    }
})

3. Pass Parameters Explicitly

// Good: explicit parameters
task := gocron.NewTask(
    func(id int, name string) {
        process(id, name)
    },
    userID,
    userName,
)

// Bad: closure capture (harder to test, debug)
task := gocron.NewTask(func() {
    process(userID, userName)
})

4. Return Errors for Error Handling

task := gocron.NewTask(func() error {
    if err := doWork(); err != nil {
        return fmt.Errorf("work failed: %w", err)
    }
    return nil
})

Use with event listeners to capture errors:

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    task,
    gocron.WithEventListeners(
        gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
            log.Printf("Job failed: %v", err)
        }),
    ),
)

5. Name Your Jobs

j, _ := s.NewJob(
    gocron.DurationJob(time.Minute),
    gocron.NewTask(myFunc),
    gocron.WithName("my-cleanup-job"),
)

Named jobs are easier to identify in logs and metrics.

Related Documentation

Install with Tessl CLI

npx tessl i tessl/golang-github-com-go-co-op-gocron-v2

docs

index.md

tile.json