Skip to content

goforj/scheduler

Repository files navigation

scheduler logo

A fluent, Laravel-inspired scheduler for Go that wraps gocron with expressive APIs for defining, filtering, and controlling scheduled jobs.

Go Reference License: MIT Go Test Go version Latest tag Tests Go Report Card

Features

  • Fluent, chainable API for intervals, cron strings, and calendar helpers (daily/weekly/monthly).
  • Overlap protection with optional distributed locking plus per-job tags and metadata.
  • Filters (weekdays/weekends/time windows) and hooks (before/after/success/failure) keep jobs predictable.
  • Command execution helper for running CLI tasks with background mode and env-aware tagging.
  • Auto-generated, compile-tested examples ensure docs and behavior stay in sync.

Why scheduler?

Go has excellent low-level scheduling libraries, but defining real-world schedules often turns into a maze of cron strings, conditionals, and glue code.

scheduler provides a Laravel-style fluent API on top of gocron that lets you describe when, how, and under what conditions a job should run - without hiding what’s actually happening.

Everything remains explicit, testable, and inspectable, while staying pleasant to read and maintain.

Example

scheduler.NewJobBuilder(s).
    Name("reports:generate").
    Weekdays().
    Between("09:00", "17:00").
    WithoutOverlapping().
    DailyAt("10:30").
    Do(func() {
    generateReports()
})

List jobs as an ASCII table

package main

import (
	"github.com/go-co-op/gocron/v2"
	"github.com/goforj/scheduler"
)

func main() {
	s, _ := gocron.NewScheduler()
	s.Start()
	defer s.Shutdown()

	scheduler.NewJobBuilder(s).
		EveryMinute().
		Name("cleanup").
		Do(func() {})

	scheduler.NewJobBuilder(s).PrintJobsList()
}

Example output:

+--------------------------------------------------------------------------------------+
| Scheduler Jobs › (3)
+----------------+----------+----------------+---------+--------+----------------------+
| Name           | Type     | Schedule       | Handler | Next   | Tags                 |
+----------------+----------+----------------+---------+--------+----------------------+
| hello:world    | command  | cron 0 0 * * 0 | -       | in 3d  | env=dev, args="w"    |
| hello:world    | command  | every 1h       | -       | in 1h  | env=dev, args="hour" |
| cleanup        | function | every 1m       | cleanup | in 1m  | env=dev              |
+----------------+----------+----------------+---------+--------+----------------------+

Runnable examples

Every function has a corresponding runnable example under ./examples.

These examples are generated directly from the documentation blocks of each function, ensuring the docs and code never drift. These are the same examples you see here in the README and GoDoc.

An automated test executes every example to verify it builds and runs successfully.

This guarantees all examples are valid, up-to-date, and remain functional as the API evolves.

API Index

Group Functions
Adapters Lock Run Unlock
Commands Command
Concurrency WithoutOverlapping WithoutOverlappingWithLocker
Configuration Timezone WithCommandRunner WithNowFunc
Construction NewJobBuilder
Diagnostics CronExpr Error Job PrintJobsList
Execution RunInBackground
Filters Between Days Environments Fridays Mondays Saturdays Skip Sundays Thursdays Tuesdays UnlessBetween Wednesdays Weekdays Weekends When
Hooks After Before OnFailure OnSuccess
Locking NewRedisLocker
Metadata JobMetadata Name
Scheduling Cron Daily DailyAt DaysOfMonth Do Every EveryFifteenMinutes EveryFifteenSeconds EveryFiveMinutes EveryFiveSeconds EveryFourHours EveryFourMinutes EveryMinute EveryOddHour EverySecond EverySixHours EveryTenMinutes EveryTenSeconds EveryThirtyMinutes EveryThirtySeconds EveryThreeHours EveryThreeMinutes EveryTwentySeconds EveryTwoHours EveryTwoMinutes EveryTwoSeconds Hourly HourlyAt Hours LastDayOfMonth Minutes Monthly MonthlyOn Quarterly QuarterlyOn Seconds TwiceDaily TwiceDailyAt TwiceMonthly Weekly WeeklyOn Yearly YearlyOn
State management RetainState

Adapters

Lock

Lock invokes the underlying function.

client := redis.NewClient(&redis.Options{})
locker := scheduler.NewRedisLocker(client, time.Minute)
lock, _ := locker.Lock(context.Background(), "job")
_ = lock.Unlock(context.Background())

Run

Run executes the underlying function.

runner := scheduler.CommandRunnerFunc(func(ctx context.Context, exe string, args []string) error {
	return nil
})
_ = runner.Run(context.Background(), "echo", []string{"hi"})

Unlock

Unlock invokes the underlying function.

Commands

Command

Command executes the current binary with the given subcommand and variadic args.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
	Cron("0 0 * * *").
	Command("jobs:purge", "--force")

Concurrency

WithoutOverlapping

WithoutOverlapping ensures the job does not run concurrently.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
	WithoutOverlapping().
	EveryFiveSeconds().
	Do(func() { time.Sleep(7 * time.Second) })

WithoutOverlappingWithLocker

WithoutOverlappingWithLocker ensures the job does not run concurrently across distributed systems using the provided locker.

locker := scheduler.LockerFunc(func(ctx context.Context, key string) (gocron.Lock, error) {
	return scheduler.LockFunc(func(context.Context) error { return nil }), nil
})

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
	WithoutOverlappingWithLocker(locker).
	EveryMinute().
	Do(func() {})

Configuration

Timezone

Timezone sets a timezone string for the job (not currently applied to gocron Scheduler).

scheduler.NewJobBuilder(nil).
	Timezone("America/New_York").
	Daily()

WithCommandRunner

WithCommandRunner overrides command execution (default: exec.CommandContext).

runner := scheduler.CommandRunnerFunc(func(_ context.Context, exe string, args []string) error {
	fmt.Println(exe, args)
	return nil
})

builder := scheduler.NewJobBuilder(nil).
	WithCommandRunner(runner)
fmt.Printf("%T\n", builder)

WithNowFunc

WithNowFunc overrides current time (default: time.Now). Useful for tests.

fixed := func() time.Time { return time.Unix(0, 0) }
scheduler.NewJobBuilder(nil).WithNowFunc(fixed)

Construction

NewJobBuilder

NewJobBuilder creates a new JobBuilder with the provided scheduler.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()
scheduler.NewJobBuilder(s).EverySecond().Do(func() {})

Diagnostics

CronExpr

CronExpr returns the cron expression string configured for this job.

builder := scheduler.NewJobBuilder(nil).Cron("0 9 * * *")
fmt.Println(builder.CronExpr())

Error

Error returns the error if any occurred during job scheduling.

builder := scheduler.NewJobBuilder(nil).DailyAt("bad")
fmt.Println(builder.Error())

Job

Job returns the last scheduled gocron.Job instance, if available.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

b := scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
fmt.Println(b.Job() != nil)

PrintJobsList

PrintJobsList renders and prints the scheduler job table to stdout.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
	EverySecond().
	Name("heartbeat").
	Do(func() {})

scheduler.NewJobBuilder(s).PrintJobsList()

Execution

RunInBackground

RunInBackground runs command/exec tasks in a goroutine.

scheduler.NewJobBuilder(nil).
	RunInBackground().
	Command("noop")

Filters

Between

Between limits the job to run between the provided HH:MM times (inclusive).

scheduler.NewJobBuilder(nil).
	Between("09:00", "17:00").
	EveryMinute()

Days

Days limits the job to a specific set of weekdays.

scheduler.NewJobBuilder(nil).
	Days(time.Monday, time.Wednesday, time.Friday).
	DailyAt("07:00")

Environments

Environments restricts job registration to specific environment names (e.g. "production", "staging").

scheduler.NewJobBuilder(nil).Environments("production").Daily()

Fridays

Fridays limits the job to Fridays.

scheduler.NewJobBuilder(nil).Fridays().DailyAt("09:00")

Mondays

Mondays limits the job to Mondays.

scheduler.NewJobBuilder(nil).Mondays().DailyAt("09:00")

Saturdays

Saturdays limits the job to Saturdays.

scheduler.NewJobBuilder(nil).Saturdays().DailyAt("09:00")

Skip

Skip prevents scheduling the job if the provided condition returns true.

enabled := false
scheduler.NewJobBuilder(nil).
	Skip(func() bool { return !enabled }).
	Daily()

Sundays

Sundays limits the job to Sundays.

scheduler.NewJobBuilder(nil).Sundays().DailyAt("09:00")

Thursdays

Thursdays limits the job to Thursdays.

scheduler.NewJobBuilder(nil).Thursdays().DailyAt("09:00")

Tuesdays

Tuesdays limits the job to Tuesdays.

scheduler.NewJobBuilder(nil).Tuesdays().DailyAt("09:00")

UnlessBetween

UnlessBetween prevents the job from running between the provided HH:MM times.

scheduler.NewJobBuilder(nil).
	UnlessBetween("22:00", "06:00").
	EveryMinute()

Wednesdays

Wednesdays limits the job to Wednesdays.

scheduler.NewJobBuilder(nil).Wednesdays().DailyAt("09:00")

Weekdays

Weekdays limits the job to run only on weekdays (Mon-Fri).

scheduler.NewJobBuilder(nil).Weekdays().DailyAt("09:00")

Weekends

Weekends limits the job to run only on weekends (Sat-Sun).

scheduler.NewJobBuilder(nil).Weekends().DailyAt("10:00")

When

When only schedules the job if the provided condition returns true.

flag := true
scheduler.NewJobBuilder(nil).
	When(func() bool { return flag }).
	Daily()

Hooks

After

After sets a hook to run after task execution.

scheduler.NewJobBuilder(nil).
	After(func() { println("after") }).
	Daily()

Before

Before sets a hook to run before task execution.

scheduler.NewJobBuilder(nil).
	Before(func() { println("before") }).
	Daily()

OnFailure

OnFailure sets a hook to run after failed task execution.

scheduler.NewJobBuilder(nil).
	OnFailure(func() { println("failure") }).
	Daily()

OnSuccess

OnSuccess sets a hook to run after successful task execution.

scheduler.NewJobBuilder(nil).
	OnSuccess(func() { println("success") }).
	Daily()

Locking

NewRedisLocker

NewRedisLocker creates a RedisLocker with a client and TTL.

client := redis.NewClient(&redis.Options{}) // replace with your client
locker := scheduler.NewRedisLocker(client, time.Minute)
_, _ = locker.Lock(context.Background(), "job")

Metadata

JobMetadata

JobMetadata returns a copy of the tracked job metadata keyed by job ID.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

b := scheduler.NewJobBuilder(s).EverySecond().Do(func() {})
for id, meta := range b.JobMetadata() {
	_ = id
	_ = meta.Name
}

Name

Name sets an explicit job name.

scheduler.NewJobBuilder(nil).
	Name("cache:refresh").
	HourlyAt(15)

Scheduling

Cron

Cron sets the cron expression for the job.

builder := scheduler.NewJobBuilder(nil).Cron("15 3 * * *")
fmt.Println(builder.CronExpr())

Daily

Daily schedules the job to run once per day at midnight.

scheduler.NewJobBuilder(nil).Daily()

DailyAt

DailyAt schedules the job to run daily at a specific time (e.g., "13:00").

scheduler.NewJobBuilder(nil).DailyAt("12:30")

DaysOfMonth

DaysOfMonth schedules the job to run on specific days of the month at a given time.

scheduler.NewJobBuilder(nil).DaysOfMonth([]int{5, 20}, "07:15")

Do

Do schedules the job with the provided task function.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
Name("cleanup").
Cron("0 0 * * *").
Do(func() {})

Every

Every schedules a job to run every X seconds, minutes, or hours.

scheduler.NewJobBuilder(nil).
	Every(10).
	Minutes()

EveryFifteenMinutes

EveryFifteenMinutes schedules the job to run every 15 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryFifteenMinutes().Do(func() {})

EveryFifteenSeconds

EveryFifteenSeconds schedules the job to run every 15 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryFifteenSeconds().Do(func() {})

EveryFiveMinutes

EveryFiveMinutes schedules the job to run every 5 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryFiveMinutes().Do(func() {})

EveryFiveSeconds

EveryFiveSeconds schedules the job to run every 5 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryFiveSeconds().Do(func() {})

EveryFourHours

EveryFourHours schedules the job to run every four hours at the specified minute.

scheduler.NewJobBuilder(nil).EveryFourHours(25)

EveryFourMinutes

EveryFourMinutes schedules the job to run every 4 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryFourMinutes().Do(func() {})

EveryMinute

EveryMinute schedules the job to run every 1 minute.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryMinute().Do(func() {})

EveryOddHour

EveryOddHour schedules the job to run every odd-numbered hour at the specified minute.

scheduler.NewJobBuilder(nil).EveryOddHour(10)

EverySecond

EverySecond schedules the job to run every 1 second.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EverySecond().Do(func() {})

EverySixHours

EverySixHours schedules the job to run every six hours at the specified minute.

scheduler.NewJobBuilder(nil).EverySixHours(30)

EveryTenMinutes

EveryTenMinutes schedules the job to run every 10 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryTenMinutes().Do(func() {})

EveryTenSeconds

EveryTenSeconds schedules the job to run every 10 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryTenSeconds().Do(func() {})

EveryThirtyMinutes

EveryThirtyMinutes schedules the job to run every 30 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryThirtyMinutes().Do(func() {})

EveryThirtySeconds

EveryThirtySeconds schedules the job to run every 30 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryThirtySeconds().Do(func() {})

EveryThreeHours

EveryThreeHours schedules the job to run every three hours at the specified minute.

scheduler.NewJobBuilder(nil).EveryThreeHours(20)

EveryThreeMinutes

EveryThreeMinutes schedules the job to run every 3 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryThreeMinutes().Do(func() {})

EveryTwentySeconds

EveryTwentySeconds schedules the job to run every 20 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryTwentySeconds().Do(func() {})

EveryTwoHours

EveryTwoHours schedules the job to run every two hours at the specified minute.

scheduler.NewJobBuilder(nil).EveryTwoHours(15)

EveryTwoMinutes

EveryTwoMinutes schedules the job to run every 2 minutes.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryTwoMinutes().Do(func() {})

EveryTwoSeconds

EveryTwoSeconds schedules the job to run every 2 seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).EveryTwoSeconds().Do(func() {})

Hourly

Hourly schedules the job to run every hour.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).Hourly().Do(func() {})

HourlyAt

HourlyAt schedules the job to run every hour at the specified minute.

scheduler.NewJobBuilder(nil).HourlyAt(5)

Hours

Hours schedules the job to run every X hours.

scheduler.NewJobBuilder(nil).Every(6).Hours()

LastDayOfMonth

LastDayOfMonth schedules the job to run on the last day of each month at a specific time.

scheduler.NewJobBuilder(nil).LastDayOfMonth("23:30")

Minutes

Minutes schedules the job to run every X minutes.

scheduler.NewJobBuilder(nil).Every(15).Minutes()

Monthly

Monthly schedules the job to run on the first day of each month at midnight.

scheduler.NewJobBuilder(nil).Monthly()

MonthlyOn

MonthlyOn schedules the job to run on a specific day of the month at a given time.

scheduler.NewJobBuilder(nil).MonthlyOn(15, "09:30")

Quarterly

Quarterly schedules the job to run on the first day of each quarter at midnight.

scheduler.NewJobBuilder(nil).Quarterly()

QuarterlyOn

QuarterlyOn schedules the job to run on a specific day of each quarter at a given time.

scheduler.NewJobBuilder(nil).QuarterlyOn(3, "12:00")

Seconds

Seconds schedules the job to run every X seconds.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

scheduler.NewJobBuilder(s).
	Every(3).
	Seconds().
	Do(func() {})

TwiceDaily

TwiceDaily schedules the job to run daily at two specified hours (e.g., 1 and 13).

scheduler.NewJobBuilder(nil).TwiceDaily(1, 13)

TwiceDailyAt

TwiceDailyAt schedules the job to run daily at two specified times (e.g., 1:15 and 13:15).

scheduler.NewJobBuilder(nil).TwiceDailyAt(1, 13, 15)

TwiceMonthly

TwiceMonthly schedules the job to run on two specific days of the month at the given time.

scheduler.NewJobBuilder(nil).TwiceMonthly(1, 15, "10:00")

Weekly

Weekly schedules the job to run once per week on Sunday at midnight.

scheduler.NewJobBuilder(nil).Weekly()

WeeklyOn

WeeklyOn schedules the job to run weekly on a specific day of the week and time. Day uses 0 = Sunday through 6 = Saturday.

scheduler.NewJobBuilder(nil).WeeklyOn(1, "8:00")

Yearly

Yearly schedules the job to run on January 1st every year at midnight.

scheduler.NewJobBuilder(nil).Yearly()

YearlyOn

YearlyOn schedules the job to run every year on a specific month, day, and time.

scheduler.NewJobBuilder(nil).YearlyOn(12, 25, "06:45")

State management

RetainState

RetainState allows the job to retain its state after execution.

s, _ := gocron.NewScheduler()
s.Start()
defer s.Shutdown()

builder := scheduler.NewJobBuilder(s).EverySecond().RetainState()
builder.Do(func() {})
builder.Do(func() {})