gocron

package module
v0.0.0-...-85e351f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2023 License: MIT Imports: 14 Imported by: 0

README

gocron: A Golang Job Scheduling Package.

Mentioned in Awesome Go CI State Go Report Card Go Doc

gocron is a job scheduling package which lets you run Go functions at pre-determined intervals using a simple, human-friendly syntax.

gocron is a Golang scheduler implementation similar to the Ruby module clockwork and the Python job scheduling package schedule.

See also these two great articles that were used for design input:

If you want to chat, you can find us at Slack!

Concepts

  • Scheduler: The scheduler tracks all the jobs assigned to it and makes sure they are passed to the executor when ready to be run. The scheduler is able to manage overall aspects of job behavior like limiting how many jobs are running at one time.
  • Job: The job is simply aware of the task (go function) it's provided and is therefore only able to perform actions related to that task like preventing itself from overruning a previous task that is taking a long time.
  • Executor: The executor, as it's name suggests, is simply responsible for calling the task (go function) that the job hands to it when sent by the scheduler.

Examples

s := gocron.NewScheduler(time.UTC)

// Every starts the job immediately and then runs at the
// specified interval
job, err := s.Every(5).Seconds().Do(func(){ ... })
if err != nil {
	// handle the error related to setting up the job
}

// to wait for the interval to pass before running the first job
// use WaitForSchedule or WaitForScheduleAll
s.Every(5).Second().WaitForSchedule().Do(func(){ ... })

s.WaitForScheduleAll()
s.Every(5).Second().Do(func(){ ... }) // waits for schedule
s.Every(5).Second().Do(func(){ ... }) // waits for schedule

// strings parse to duration
s.Every("5m").Do(func(){ ... })

s.Every(5).Days().Do(func(){ ... })

s.Every(1).Month(1, 2, 3).Do(func(){ ... })

// set time
s.Every(1).Day().At("10:30").Do(func(){ ... })

// set multiple times
s.Every(1).Day().At("10:30;08:00").Do(func(){ ... })

s.Every(1).Day().At("10:30").At("08:00").Do(func(){ ... })

// Schedule each last day of the month
s.Every(1).MonthLastDay().Do(func(){ ... })

// Or each last day of every other month
s.Every(2).MonthLastDay().Do(func(){ ... })

// cron expressions supported
s.Cron("*/1 * * * *").Do(task) // every minute

// cron second-level expressions supported
s.CronWithSeconds("*/1 * * * * *").Do(task) // every second

// you can start running the scheduler in two different ways:
// starts the scheduler asynchronously
s.StartAsync()
// starts the scheduler and blocks current execution path
s.StartBlocking()

// stop the running scheduler in two different ways:
// stop the scheduler
s.Stop()

// stop the scheduler and notify the `StartBlocking()` to exit
s.StopBlockingChan()

For more examples, take a look in our go docs

Options

Interval Supported schedule options
sub-second StartAt()
milliseconds StartAt()
seconds StartAt()
minutes StartAt()
hours StartAt()
days StartAt(), At()
weeks StartAt(), At(), Weekday() (and all week day named functions)
months StartAt(), At()

There are several options available to restrict how jobs run:

Mode Function Behavior
Default jobs are rescheduled at every interval
Job singleton SingletonMode() a long running job will not be rescheduled until the current run is completed
Scheduler limit SetMaxConcurrentJobs() set a collective maximum number of concurrent jobs running across the scheduler
Distributed locking WithDistributedLocker() prevents the same job from being run more than once when running multiple instances of the scheduler
Distributed elector WithDistributedElector() multiple instances exist in a distributed scenario, only the leader instance can run jobs

Distributed Locker Implementations

  • Redis: redislock go get github.com/go-co-op/gocron-redis-lock

Tags

Jobs may have arbitrary tags added which can be useful when tracking many jobs. The scheduler supports both enforcing tags to be unique and when not unique, running all jobs with a given tag.

s := gocron.NewScheduler(time.UTC)
s.TagsUnique()

_, _ = s.Every(1).Week().Tag("foo").Do(task)
_, err := s.Every(1).Week().Tag("foo").Do(task)
// error!!!

s := gocron.NewScheduler(time.UTC)

s.Every(2).Day().Tag("tag").At("10:00").Do(task)
s.Every(1).Minute().Tag("tag").Do(task)
s.RunByTag("tag")
// both jobs will run

FAQ

  • Q: I'm running multiple pods on a distributed environment. How can I make a job not run once per pod causing duplication?

    • We recommend using your own lock solution within the jobs themselves (you could use Redis, for example)
    • A2: Use the scheduler option WithDistributedLocker and either use an implemented backend or implement your own and contribute it back in a PR!
  • Q: I've removed my job from the scheduler, but how can I stop a long-running job that has already been triggered?

    • A: We recommend using a means of canceling your job, e.g. a context.WithCancel().

    • A2: You can listen to the job context Done channel to know when the job has been canceled

      task := func(in string, job gocron.Job) {
          fmt.Printf("this job's last run: %s this job's next run: %s\n", job.LastRun(), job.NextRun())
          fmt.Printf("in argument is %s\n", in)
      
          ticker := time.NewTicker(100 * time.Millisecond)
          defer ticker.Stop()
      
          for {
              select {
              case <-job.Context().Done():
                  fmt.Printf("function has been canceled, performing cleanup and exiting gracefully\n")
                  return
              case <-ticker.C:
                  fmt.Printf("performing a hard job that takes a long time that I want to kill whenever I want\n")
              }
          }
      }
      
      var err error
      s := gocron.NewScheduler(time.UTC)
      s.SingletonModeAll()
      j, err := s.Every(1).Hour().Tag("myJob").DoWithJobDetails(task, "foo")
      if err != nil {
          log.Fatalln("error scheduling job", err)
      }
      
      s.StartAsync()
      
      // Simulate some more work
      time.Sleep(time.Second)
      
      // I want to stop the job, together with the underlying goroutine
      fmt.Printf("now I want to kill the job\n")
      err = s.RemoveByTag("myJob")
      if err != nil {
          log.Fatalln("error removing job by tag", err)
      }
      
      // Wait a bit so that we can see that the job is exiting gracefully
      time.Sleep(time.Second)
      fmt.Printf("Job: %#v, Error: %#v", j, err)
      

Looking to contribute? Try to follow these guidelines:

  • Use issues for everything
  • For a small change, just send a PR!
  • For bigger changes, please open an issue for discussion before sending a PR.
  • PRs should have: tests, documentation and examples (if it makes sense)
  • You can also contribute by:
    • Reporting issues
    • Suggesting new features or enhancements
    • Improving/fixing documentation

Design

design-diagram

Jetbrains supports this project with GoLand licenses. We appreciate their support for free and open source software!

Star History

Star History Chart

Documentation

Overview

Package gocron : A Golang Job Scheduling Package.

An in-process scheduler for periodic jobs that uses the builder pattern for configuration. gocron lets you run Golang functions periodically at pre-determined intervals using a simple, human-friendly syntax.

Index

Examples

Constants

View Source
const (
	// RescheduleMode - the default is that if a limit on maximum
	// concurrent jobs is set and the limit is reached, a job will
	// skip it's run and try again on the next occurrence in the schedule
	RescheduleMode limitMode = iota

	// WaitMode - if a limit on maximum concurrent jobs is set
	// and the limit is reached, a job will wait to try and run
	// until a spot in the limit is freed up.
	//
	// Note: this mode can produce unpredictable results as
	// job execution order isn't guaranteed. For example, a job that
	// executes frequently may pile up in the wait queue and be executed
	// many times back to back when the queue opens.
	//
	// Warning: do not use this mode if your jobs will continue to stack
	// up beyond the ability of the limit workers to keep up. An example of
	// what NOT to do:
	//
	//     s.Every("1s").Do(func() {
	//         // this will result in an ever-growing number of goroutines
	//    	   // blocked trying to send to the buffered channel
	//         time.Sleep(10 * time.Minute)
	//     })
	WaitMode
)

Variables

View Source
var (
	ErrNotAFunction                  = errors.New("gocron: only functions can be scheduled into the job queue")
	ErrNotScheduledWeekday           = errors.New("gocron: job not scheduled weekly on a weekday")
	ErrJobNotFoundWithTag            = errors.New("gocron: no jobs found with given tag")
	ErrJobNotFound                   = errors.New("gocron: no job found")
	ErrUnsupportedTimeFormat         = errors.New("gocron: the given time format is not supported")
	ErrInvalidInterval               = errors.New("gocron: .Every() interval must be greater than 0")
	ErrInvalidIntervalType           = errors.New("gocron: .Every() interval must be int, time.Duration, or string")
	ErrInvalidIntervalUnitsSelection = errors.New("gocron: .Every(time.Duration) and .Cron() cannot be used with units (e.g. .Seconds())")
	ErrInvalidFunctionParameters     = errors.New("gocron: length of function parameters must match job function parameters")

	ErrAtTimeNotSupported               = errors.New("gocron: the At() method is not supported for this time unit")
	ErrWeekdayNotSupported              = errors.New("gocron: weekday is not supported for time unit")
	ErrInvalidDayOfMonthEntry           = errors.New("gocron: only days 1 through 28 and -1 through -28 are allowed for monthly schedules")
	ErrInvalidMonthLastDayEntry         = errors.New("gocron: only a single negative integer is permitted for MonthLastDay")
	ErrTagsUnique                       = func(tag string) error { return fmt.Errorf("gocron: a non-unique tag was set on the job: %s", tag) }
	ErrWrongParams                      = errors.New("gocron: wrong list of params")
	ErrDoWithJobDetails                 = errors.New("gocron: DoWithJobDetails expects a function whose last parameter is a gocron.Job")
	ErrUpdateCalledWithoutJob           = errors.New("gocron: a call to Scheduler.Update() requires a call to Scheduler.Job() first")
	ErrCronParseFailure                 = errors.New("gocron: cron expression failed to be parsed")
	ErrInvalidDaysOfMonthDuplicateValue = errors.New("gocron: duplicate days of month is not allowed in Month() and Months() methods")
)

Error declarations for gocron related errors

View Source
var (
	ErrFailedToConnectToRedis = errors.New("gocron: failed to connect to redis")
	ErrFailedToObtainLock     = errors.New("gocron: failed to obtain lock")
	ErrFailedToReleaseLock    = errors.New("gocron: failed to release lock")
)

Functions

func SetPanicHandler

func SetPanicHandler(handler PanicHandlerFunc)

SetPanicHandler sets the global panicHandler to the given function. Leaving it nil or setting it to nil disables automatic panic handling. If the panicHandler is not nil, any panic that occurs during executing a job will be recovered and the panicHandlerFunc will be called with the job's funcName and the recover data.

Example
package main

import (
	"fmt"

	"github.com/andoma-go/gocron"
)

func main() {
	gocron.SetPanicHandler(func(jobName string, _ interface{}) {
		fmt.Printf("Panic in job: %s", jobName)
		fmt.Println("do something to handle the panic")
	})
}
Output:

Types

type Elector

type Elector interface {
	// IsLeader should return an error if the job should not be scheduled and nil if the job should be scheduled.
	IsLeader(ctx context.Context) error
}

Elector determines the leader from instances asking to be the leader. Only the leader runs jobs. If the leader goes down, a new leader will be elected.

type EventListener

type EventListener func(j *Job)

EventListener functions utilize the job's name and are triggered by or in the condition that the name suggests

func AfterJobRuns

func AfterJobRuns(eventListenerFunc func(jobName string)) EventListener

AfterJobRuns is called after the job is run This is called even when an error is returned

func BeforeJobRuns

func BeforeJobRuns(eventListenerFunc func(jobName string)) EventListener

BeforeJobRuns is called before the job is run

func WhenJobReturnsError

func WhenJobReturnsError(eventListenerFunc func(jobName string, err error)) EventListener

WhenJobReturnsError is called when the job returns an error

func WhenJobReturnsNoError

func WhenJobReturnsNoError(eventListenerFunc func(jobName string)) EventListener

WhenJobReturnsNoError is called when the job does not return an error the function must accept a single parameter, which is an error

type Job

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

Job struct stores the information necessary to run a Job

func (*Job) Context

func (j *Job) Context() context.Context

Context returns the job's context. The context controls cancellation.

func (*Job) Error

func (j *Job) Error() error

Error returns an error if one occurred while creating the Job. If multiple errors occurred, they will be wrapped and can be checked using the standard unwrap options.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, err := s.Every(1).Day().At("bad time").Do(func() {})
	fmt.Println(err)
}
Output:

gocron: the given time format is not supported

func (*Job) FinishedRunCount

func (j *Job) FinishedRunCount() int

FinishedRunCount returns the number of times the job has finished running

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	go func() {
		for {
			fmt.Println("Count of finished job runs", job.FinishedRunCount())
			time.Sleep(time.Second)
		}
	}()
	s.StartAsync()
}
Output:

func (*Job) GetName

func (j *Job) GetName() string

GetName returns the name of the current job. The name is either the name set using Job.Name() / Scheduler.Name() or the name of the funcion as Go sees it, for example `main.func1`

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Second().Name("job_name").Do(task)
	fmt.Println(j.GetName())
}
Output:

job_name

func (*Job) IsRunning

func (j *Job) IsRunning() bool

IsRunning reports whether any instances of the job function are currently running

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(10).Seconds().Do(func() { time.Sleep(2 * time.Second) })

	fmt.Println(j.IsRunning())

	s.StartAsync()

	time.Sleep(time.Second)
	fmt.Println(j.IsRunning())

	time.Sleep(time.Second)
	s.Stop()

	time.Sleep(1 * time.Second)
	fmt.Println(j.IsRunning())
}
Output:

false
true
false

func (*Job) LastRun

func (j *Job) LastRun() time.Time

LastRun returns the time the job was run last

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	s.StartAsync()

	fmt.Println("Last run:", job.LastRun())
}
Output:

func (*Job) LimitRunsTo

func (j *Job) LimitRunsTo(n int)

LimitRunsTo limits the number of executions of this job to n. Upon reaching the limit, the job is removed from the scheduler.

Note: If a job is added to a running scheduler and this method is then used you may see the job run more than the set limit as job is scheduled immediately by default upon being added to the scheduler. It is recommended to use the LimitRunsTo() func on the scheduler chain when scheduling the job. For example: scheduler.LimitRunsTo(1).Do()

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	job.LimitRunsTo(2)
	s.StartAsync()
}
Output:

func (*Job) Name

func (j *Job) Name(name string)

Name sets the name of the current job.

If the scheduler is running using WithDistributedLocker(), the job name is used as the distributed lock key.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.Every(1).Second().Name("job_name").Do(task)
}
Output:

func (*Job) NextRun

func (j *Job) NextRun() time.Time

NextRun returns the time the job will run next

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	go func() {
		for {
			fmt.Println("Next run", job.NextRun())
			time.Sleep(time.Second)
		}
	}()
	s.StartAsync()
}
Output:

func (*Job) PreviousRun

func (j *Job) PreviousRun() time.Time

PreviousRun returns the job run time previous to LastRun

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	s.StartAsync()

	fmt.Println("Previous run:", job.PreviousRun())
}
Output:

func (*Job) RegisterEventListeners

func (j *Job) RegisterEventListeners(eventListeners ...EventListener)

RegisterEventListeners accepts EventListeners and registers them for the job The event listeners are then called at the times described by each listener.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)

	job, _ := s.Every("1s").Name("my_func").Do(func() error { return fmt.Errorf("error") })
	job.RegisterEventListeners(
		gocron.AfterJobRuns(func(jobName string) {
			fmt.Printf("afterJobRuns: %s\n", jobName)
		}),
		gocron.BeforeJobRuns(func(jobName string) {
			fmt.Printf("beforeJobRuns: %s\n", jobName)
		}),
		gocron.WhenJobReturnsError(func(jobName string, err error) {
			fmt.Printf("whenJobReturnsError: %s, %v\n", jobName, err)
		}),
		gocron.WhenJobReturnsNoError(func(jobName string) {
			fmt.Printf("whenJobReturnsNoError: %s\n", jobName)
		}),
	)
	s.StartAsync()
	time.Sleep(100 * time.Millisecond)
	s.Stop()
}
Output:

beforeJobRuns: my_func
whenJobReturnsError: my_func, error
afterJobRuns: my_func

func (*Job) RunCount

func (j *Job) RunCount() int

RunCount returns the number of times the job has been started

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	go func() {
		for {
			fmt.Println("Run count", job.RunCount())
			time.Sleep(time.Second)
		}
	}()
	s.StartAsync()
}
Output:

func (*Job) ScheduledAtTime

func (j *Job) ScheduledAtTime() string

ScheduledAtTime returns the specific time of day the Job will run at. If multiple times are set, the earliest time will be returned.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Day().At("10:30").Do(task)
	s.StartAsync()
	fmt.Println(job.ScheduledAtTime())

	// if multiple times are set, the earliest time will be returned
	job1, _ := s.Every(1).Day().At("10:30;08:00").Do(task)
	fmt.Println(job1.ScheduledAtTime())
}
Output:

10:30
08:00

func (*Job) ScheduledAtTimes

func (j *Job) ScheduledAtTimes() []string

ScheduledAtTimes returns the specific times of day the Job will run at

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Day().At("10:30;08:00").Do(task)
	s.StartAsync()
	fmt.Println(job.ScheduledAtTimes())
}
Output:

[08:00 10:30]

func (*Job) ScheduledTime

func (j *Job) ScheduledTime() time.Time

ScheduledTime returns the time of the Job's next scheduled run

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Day().At("10:30").Do(task)
	s.StartAsync()
	fmt.Println(job.ScheduledTime())
}
Output:

func (*Job) SetEventListeners deprecated

func (j *Job) SetEventListeners(onBeforeJobExecution interface{}, onAfterJobExecution interface{})

Deprecated: SetEventListeners accepts two functions that will be called, one before and one after the job is run

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Week().Do(task)
	job.SetEventListeners(func() {}, func() {})
}
Output:

func (*Job) SingletonMode

func (j *Job) SingletonMode()

SingletonMode prevents a new job from starting if the prior job has not yet completed it's run Note: If a job is added to a running scheduler and this method is then used you may see the job run overrun itself as job is scheduled immediately by default upon being added to the scheduler. It is recommended to use the SingletonMode() func on the scheduler chain when scheduling the job.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every(1).Second().Do(task)
	job.SingletonMode()
}
Output:

func (*Job) Tag

func (j *Job) Tag(tags ...string)

Tag allows you to add arbitrary labels to a Job that do not impact the functionality of the Job

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every("1s").Do(task)

	job.Tag("tag1", "tag2", "tag3")
	s.StartAsync()
	fmt.Println(job.Tags())
}
Output:

[tag1 tag2 tag3]

func (*Job) Tags

func (j *Job) Tags() []string

Tags returns the tags attached to the Job

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every("1s").Do(task)

	job.Tag("tag1", "tag2", "tag3")
	s.StartAsync()
	fmt.Println(job.Tags())
}
Output:

[tag1 tag2 tag3]

func (*Job) Untag

func (j *Job) Untag(t string)

Untag removes a tag from a Job

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	job, _ := s.Every("1s").Do(task)

	job.Tag("tag1", "tag2", "tag3")
	s.StartAsync()
	fmt.Println(job.Tags())
	job.Untag("tag2")
	fmt.Println(job.Tags())
}
Output:

[tag1 tag2 tag3]
[tag1 tag3]

func (*Job) Weekday

func (j *Job) Weekday() (time.Weekday, error)

Weekday returns which day of the week the Job will run on and will return an error if the Job is not scheduled weekly

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	weeklyJob, _ := s.Every(1).Week().Monday().Do(task)
	weekday, _ := weeklyJob.Weekday()
	fmt.Println(weekday)

	dailyJob, _ := s.Every(1).Day().Do(task)
	_, err := dailyJob.Weekday()
	fmt.Println(err)
}
Output:

Monday
gocron: job not scheduled weekly on a weekday

func (*Job) Weekdays

func (j *Job) Weekdays() []time.Weekday

Weekdays returns a slice of time.Weekday that the Job will run in a week and will return an error if the Job is not scheduled weekly

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Week().Monday().Wednesday().Friday().Do(task)
	fmt.Println(j.Weekdays())
}
Output:

[Monday Wednesday Friday]

type Lock

type Lock interface {
	Unlock(ctx context.Context) error
}

Lock represents an obtained lock

type Locker

type Locker interface {
	// Lock if an error is returned by lock, the job will not be scheduled.
	Lock(ctx context.Context, key string) (Lock, error)
}

Locker represents the required interface to lock jobs when running multiple schedulers.

type PanicHandlerFunc

type PanicHandlerFunc func(jobName string, recoverData interface{})

PanicHandlerFunc represents a type that can be set to handle panics occurring during job execution.

type Scheduler

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

Scheduler struct stores a list of Jobs and the location of time used by the Scheduler

func NewScheduler

func NewScheduler(loc *time.Location) *Scheduler

NewScheduler creates a new Scheduler

func (*Scheduler) At

func (s *Scheduler) At(i interface{}) *Scheduler

At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM" or time.Time (note that only the hours, minutes, seconds and nanos are used). When the At time(s) occur on the same day on which the scheduler is started the Job will be run at the first available At time. For example: a schedule for every 2 days at 9am and 11am - currently 7am -> Job runs at 9am and 11am on the day the scheduler was started - currently 12 noon -> Job runs at 9am and 11am two days after the scheduler started

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().At("10:30").Do(task)
	_, _ = s.Every(1).Monday().At("10:30:01").Do(task)
	// multiple
	_, _ = s.Every(1).Monday().At("10:30;18:00").Do(task)
	_, _ = s.Every(1).Monday().At("10:30").At("18:00").Do(task)
}
Output:

func (*Scheduler) ChangeLocation

func (s *Scheduler) ChangeLocation(newLocation *time.Location)

ChangeLocation changes the default time location

Example
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	fmt.Println(s.Location())

	location, err := time.LoadLocation("America/Los_Angeles")
	if err != nil {
		log.Fatalf("Error loading location: %s", err)
	}
	s.ChangeLocation(location)
	fmt.Println(s.Location())
}
Output:

UTC
America/Los_Angeles

func (*Scheduler) Clear

func (s *Scheduler) Clear()

Clear clears all Jobs from this scheduler

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Second().Do(task)
	_, _ = s.Every(1).Minute().Do(task)
	_, _ = s.Every(1).Month(1).Do(task)
	fmt.Println(len(s.Jobs())) // Print the number of jobs before clearing
	s.Clear()                  // Clear all the jobs
	fmt.Println(len(s.Jobs())) // Print the number of jobs after clearing
	s.StartAsync()
}
Output:

3
0

func (*Scheduler) Cron

func (s *Scheduler) Cron(cronExpression string) *Scheduler
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// parsing handled by https://pkg.go.dev/github.com/robfig/cron/v3
	// which follows https://en.wikipedia.org/wiki/Cron
	_, _ = s.Cron("*/1 * * * *").Do(task) // every minute
	_, _ = s.Cron("0 1 * * *").Do(task)   // every day at 1 am
	_, _ = s.Cron("0 0 * * 6,0").Do(task) // weekends only
}
Output:

func (*Scheduler) CronWithSeconds

func (s *Scheduler) CronWithSeconds(cronExpression string) *Scheduler
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// parsing handled by https://pkg.go.dev/github.com/robfig/cron/v3
	// which follows https://en.wikipedia.org/wiki/Cron
	_, _ = s.CronWithSeconds("*/1 * * * * *").Do(task)  // every second
	_, _ = s.CronWithSeconds("0-30 * * * * *").Do(task) // every second 0-30
}
Output:

func (*Scheduler) CustomTime

func (s *Scheduler) CustomTime(customTimeWrapper TimeWrapper)

CustomTime takes an in a struct that implements the TimeWrapper interface allowing the caller to mock the time used by the scheduler. This is useful for tests relying on gocron.

Example
package main

import ()

func main() {
	// Implement your own custom time struct
	//
	// type myCustomTime struct{}
	//
	// var _ gocron.TimeWrapper = (*myCustomTime)(nil)
	//
	// func (m myCustomTime) Now(loc *time.Location) time.Time {
	//	 panic("implement me")
	// }
	//
	// func (m myCustomTime) Sleep(duration time.Duration) {
	//	 panic("implement me")
	// }
	//
	// func (m myCustomTime) Unix(sec int64, nsec int64) time.Time {
	//	 panic("implement me")
	// }
	//
	// mct := myCustomTime{}
	//
	// s := gocron.NewScheduler(time.UTC)
	// s.CustomTime(mct)
}
Output:

func (*Scheduler) CustomTimer

func (s *Scheduler) CustomTimer(customTimer func(d time.Duration, f func()) *time.Timer)

CustomTimer takes in a function that mirrors the time.AfterFunc This is used to mock the time.AfterFunc function used by the scheduler for testing long intervals in a short amount of time.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.CustomTimer(func(d time.Duration, f func()) *time.Timer {
		// force jobs with 1 minute interval to run every second
		if d == time.Minute {
			d = time.Second
		}
		return time.AfterFunc(d, f)
	})
	// this job will run every 1 second
	_, _ = s.Every("1m").Do(task)
}
Output:

func (*Scheduler) Day

func (s *Scheduler) Day() *Scheduler

Day sets the unit with days

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("24h").Do(task)
	_, _ = s.Every(1).Day().Do(task)
	_, _ = s.Every(1).Days().Do(task)
}
Output:

func (*Scheduler) Days

func (s *Scheduler) Days() *Scheduler

Days set the unit with days

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("24h").Do(task)
	_, _ = s.Every(1).Day().Do(task)
	_, _ = s.Every(1).Days().Do(task)
}
Output:

func (*Scheduler) Do

func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error)

Do specifies the jobFunc that should be called every time the Job runs

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j1, err := s.Every(1).Second().Do(task)
	fmt.Printf("Job: %v, Error: %v", j1, err)

	taskWithParameters := func(param1, param2 string) {}
	j2, err := s.Every(1).Second().Do(taskWithParameters, "param1", "param2")
	fmt.Printf("Job: %v, Error: %v", j2, err)
	s.StartAsync()
}
Output:

func (*Scheduler) DoWithJobDetails

func (s *Scheduler) DoWithJobDetails(jobFun interface{}, params ...interface{}) (*Job, error)

DoWithJobDetails specifies the jobFunc that should be called every time the Job runs and additionally passes the details of the current job to the jobFunc. The last argument of the function must be a gocron.Job that will be passed by the scheduler when the function is called.

Example
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	task := func(in string, job gocron.Job) {
		fmt.Printf("this job's last run: %s this job's next run: %s\n", job.LastRun(), job.NextRun())
		fmt.Printf("in argument is %s\n", in)

		ticker := time.NewTicker(100 * time.Millisecond)
		defer ticker.Stop()

		for {
			select {
			case <-job.Context().Done():
				fmt.Printf("function has been canceled, performing cleanup and exiting gracefully\n")
				return
			case <-ticker.C:
				fmt.Printf("performing a hard job that takes a long time that I want to kill whenever I want\n")
			}
		}
	}

	var err error
	s := gocron.NewScheduler(time.UTC)
	s.SingletonModeAll()
	j, err := s.Every(1).Hour().Tag("myJob").DoWithJobDetails(task, "foo")
	if err != nil {
		log.Fatalln("error scheduling job", err)
	}

	s.StartAsync()

	// Simulate some more work
	time.Sleep(time.Second)

	// I want to stop the job, together with the underlying goroutine
	fmt.Printf("now I want to kill the job\n")
	err = s.RemoveByTag("myJob")
	if err != nil {
		log.Fatalln("error removing job by tag", err)
	}

	// Wait a bit so that we can see that the job is exiting gracefully
	time.Sleep(time.Second)
	fmt.Printf("Job: %#v, Error: %#v", j, err)
}
Output:

func (*Scheduler) Every

func (s *Scheduler) Every(interval interface{}) *Scheduler

Every schedules a new periodic Job with an interval. Interval can be an int, time.Duration or a string that parses with time.ParseDuration(). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".

The job is run immediately, unless: * StartAt or At is set on the job, * WaitForSchedule is set on the job, * or WaitForScheduleAll is set on the scheduler.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Second().Do(task)
	_, _ = s.Every(1 * time.Second).Do(task)
	_, _ = s.Every("1s").Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) EveryRandom

func (s *Scheduler) EveryRandom(lower, upper int) *Scheduler

EveryRandom schedules a new period Job that runs at random intervals between the provided lower (inclusive) and upper (inclusive) bounds. The default unit is Seconds(). Call a different unit in the chain if you would like to change that. For example, Minutes(), Hours(), etc.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// every 1 - 5 seconds randomly
	_, _ = s.EveryRandom(1, 5).Seconds().Do(task)

	// every 5 - 10 hours randomly
	_, _ = s.EveryRandom(5, 10).Hours().Do(task)

	s.StartAsync()
}
Output:

func (*Scheduler) FindJobsByTag

func (s *Scheduler) FindJobsByTag(tags ...string) ([]*Job, error)

FindJobsByTag will return a slice of jobs that match all given tags

func (*Scheduler) Friday

func (s *Scheduler) Friday() *Scheduler

Friday sets the start day as Friday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Friday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Friday

func (*Scheduler) GetAllTags

func (s *Scheduler) GetAllTags() []string

GetAllTags returns all tags.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Second().Tag("tag1").Do(task)
	_, _ = s.Every(1).Second().Tag("tag2").Do(task)
	fmt.Println(s.GetAllTags())
}
Output:

func (*Scheduler) Hour

func (s *Scheduler) Hour() *Scheduler

Hour sets the unit with hours

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1h").Do(task)
	_, _ = s.Every(1).Hour().Do(task)
	_, _ = s.Every(1).Hours().Do(task)
}
Output:

func (*Scheduler) Hours

func (s *Scheduler) Hours() *Scheduler

Hours sets the unit with hours

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1h").Do(task)
	_, _ = s.Every(1).Hour().Do(task)
	_, _ = s.Every(1).Hours().Do(task)
}
Output:

func (*Scheduler) IsRunning

func (s *Scheduler) IsRunning() bool

IsRunning returns true if the scheduler is running

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1s").Do(task)
	fmt.Println(s.IsRunning())
	s.StartAsync()
	fmt.Println(s.IsRunning())
}
Output:

false
true

func (*Scheduler) Job

func (s *Scheduler) Job(j *Job) *Scheduler

Job puts the provided job in focus for the purpose of making changes to the job with the scheduler chain and finalized by calling Update()

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every("1s").Do(func() {})
	s.StartAsync()

	time.Sleep(10 * time.Second)
	_, _ = s.Job(j).Every("10m").Update()

	time.Sleep(30 * time.Minute)
	_, _ = s.Job(j).Every(1).Day().At("02:00").Update()
}
Output:

func (*Scheduler) Jobs

func (s *Scheduler) Jobs() []*Job

Jobs returns the list of Jobs from the scheduler

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	fmt.Println(len(s.Jobs()))
}
Output:

3

func (*Scheduler) JobsMap

func (s *Scheduler) JobsMap() map[uuid.UUID]*Job

JobsMap returns a map of job uuid to job

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	fmt.Printf("map of job uuid to job: %+v", s.JobsMap())
}
Output:

func (*Scheduler) Len

func (s *Scheduler) Len() int

Len returns the number of Jobs in the Scheduler

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	_, _ = s.Every("1s").Do(task)
	fmt.Println(s.Len())
}
Output:

3

func (*Scheduler) LimitRunsTo

func (s *Scheduler) LimitRunsTo(i int) *Scheduler

LimitRunsTo limits the number of executions of this job to n. Upon reaching the limit, the job is removed from the scheduler.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	j, _ := s.Every(1).Second().LimitRunsTo(1).Do(task)
	s.StartAsync()

	time.Sleep(time.Millisecond * 100)
	fmt.Println(j.RunCount())
}
Output:

1

func (*Scheduler) Location

func (s *Scheduler) Location() *time.Location

Location provides the current location set on the scheduler

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	fmt.Println(s.Location())
}
Output:

UTC

func (*Scheduler) Midday

func (s *Scheduler) Midday() *Scheduler
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().Midday().Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) Millisecond

func (s *Scheduler) Millisecond() *Scheduler

Millisecond sets the unit with milliseconds

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Millisecond().Do(task)
	_, _ = s.Every(1).Milliseconds().Do(task)
	_, _ = s.Every("1ms").Seconds().Do(task)
	_, _ = s.Every(time.Millisecond).Seconds().Do(task)
}
Output:

func (*Scheduler) Milliseconds

func (s *Scheduler) Milliseconds() *Scheduler

Milliseconds sets the unit with milliseconds

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Millisecond().Do(task)
	_, _ = s.Every(1).Milliseconds().Do(task)
	_, _ = s.Every("1ms").Seconds().Do(task)
	_, _ = s.Every(time.Millisecond).Seconds().Do(task)
}
Output:

func (*Scheduler) Minute

func (s *Scheduler) Minute() *Scheduler

Minute sets the unit with minutes

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1m").Do(task)
	_, _ = s.Every(1).Minute().Do(task)
	_, _ = s.Every(1).Minutes().Do(task)
}
Output:

func (*Scheduler) Minutes

func (s *Scheduler) Minutes() *Scheduler

Minutes sets the unit with minutes

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every("1m").Do(task)
	_, _ = s.Every(1).Minute().Do(task)
	_, _ = s.Every(1).Minutes().Do(task)
}
Output:

func (*Scheduler) Monday

func (s *Scheduler) Monday() *Scheduler

Monday sets the start day as Monday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Monday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Monday

func (*Scheduler) Month

func (s *Scheduler) Month(daysOfMonth ...int) *Scheduler

Month sets the unit with months Note: Only days 1 through 28 are allowed for monthly schedules Note: Multiple of the same day of month is not allowed Note: Negative numbers are special values and can only occur as single argument and count backwards from the end of the month -1 == last day of the month, -2 == penultimate day of the month

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Month().Do(task)
	_, _ = s.Every(1).Month(1).Do(task)
	_, _ = s.Every(1).Months(1).Do(task)
	_, _ = s.Every(1).Month(1, 2).Do(task)
	_, _ = s.Month(1, 2).Every(1).Do(task)
	_, _ = s.Every(1).Month(-1).Do(task)
	_, _ = s.Every(1).Month(-2).Do(task)
}
Output:

func (*Scheduler) MonthFirstWeekday

func (s *Scheduler) MonthFirstWeekday(weekday time.Weekday) *Scheduler

MonthFirstWeekday sets the job to run the first specified weekday of the month

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.MonthFirstWeekday(time.Monday).Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) MonthLastDay

func (s *Scheduler) MonthLastDay(dayCountBeforeLastDayOfMonth ...int) *Scheduler

MonthLastDay sets the unit with months at every last day of the month The optional parameter is a negative integer denoting days previous to the last day of the month. E.g. -1 == the penultimate day of the month, -2 == two days for the last day of the month

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).MonthLastDay().Do(task)
	_, _ = s.Every(2).MonthLastDay().Do(task)
	_, _ = s.Every(1).MonthLastDay(-1).Do(task)
	_, _ = s.Every(1).MonthLastDay(-2).Do(task)
}
Output:

func (*Scheduler) Months

func (s *Scheduler) Months(daysOfTheMonth ...int) *Scheduler

Months sets the unit with months Note: Only days 1 through 28 are allowed for monthly schedules Note: Multiple of the same day of month is not allowed Note: Negative numbers are special values and can only occur as single argument and count backwards from the end of the month -1 == last day of the month, -2 == penultimate day of the month

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Month(1).Do(task)
	_, _ = s.Every(1).Months(1).Do(task)
	_, _ = s.Every(1).Months(-1).Do(task)
	_, _ = s.Every(1).Months(-2).Do(task)
}
Output:

func (*Scheduler) Name

func (s *Scheduler) Name(name string) *Scheduler

Name sets the name of the current job.

If the scheduler is running using WithDistributedLocker(), the job name is used as the distributed lock key. If the job name is not set, the function name is used as the distributed lock key.

func (*Scheduler) NextRun

func (s *Scheduler) NextRun() (*Job, time.Time)

NextRun datetime when the next Job should run.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().At("10:30").Do(task)
	s.StartAsync()
	s.NextRun()
}
Output:

func (*Scheduler) PauseJobExecution

func (s *Scheduler) PauseJobExecution(shouldPause bool)
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every("1s").Do(task)
	s.StartAsync()
	s.PauseJobExecution(true)
	// jobs don't run
	s.PauseJobExecution(false)
	// jobs run again
}
Output:

func (*Scheduler) RegisterEventListeners

func (s *Scheduler) RegisterEventListeners(eventListeners ...EventListener)

RegisterEventListeners accepts EventListeners and registers them for all jobs in the scheduler at the time this function is called. The event listeners are then called at the times described by each listener. If a new job is added, an additional call to this method, or the job specific version must be executed in order for the new job to trigger event listeners.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)

	s.Every("1s").Name("my_func_1").Do(func() error { return fmt.Errorf("error_1") })
	s.Every("1s").Name("my_func_2").
		StartAt(time.Now().UTC().Add(50 * time.Millisecond)).
		Do(func() error { return fmt.Errorf("error_2") })

	s.RegisterEventListeners(
		gocron.AfterJobRuns(func(jobName string) {
			fmt.Printf("afterJobRuns: %s\n", jobName)
		}),
		gocron.BeforeJobRuns(func(jobName string) {
			fmt.Printf("beforeJobRuns: %s\n", jobName)
		}),
		gocron.WhenJobReturnsError(func(jobName string, err error) {
			fmt.Printf("whenJobReturnsError: %s, %v\n", jobName, err)
		}),
		gocron.WhenJobReturnsNoError(func(jobName string) {
			fmt.Printf("whenJobReturnsNoError: %s\n", jobName)
		}),
	)
	s.StartAsync()
	time.Sleep(120 * time.Millisecond)
	s.Stop()
}
Output:

beforeJobRuns: my_func_1
whenJobReturnsError: my_func_1, error_1
afterJobRuns: my_func_1
beforeJobRuns: my_func_2
whenJobReturnsError: my_func_2, error_2
afterJobRuns: my_func_2

func (*Scheduler) Remove

func (s *Scheduler) Remove(job interface{})

Remove specific Job by function

Removing a job stops that job's timer. However, if a job has already been started by the job's timer before being removed, the only way to stop it through gocron is to use DoWithJobDetails and access the job's Context which informs you when the job has been canceled.

Alternatively, the job function would need to have implemented a means of stopping, e.g. using a context.WithCancel() passed as params to Do method.

The above are based on what the underlying library suggests https://pkg.go.dev/time#Timer.Stop.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Week().Do(task)
	s.StartAsync()
	s.Remove(task)
	fmt.Println(s.Len())
}
Output:

0

func (*Scheduler) RemoveByID

func (s *Scheduler) RemoveByID(job *Job) error

RemoveByID removes the job from the scheduler looking up by id

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	j, _ := s.Every(1).Week().Do(task)
	_, _ = s.Every(1).Week().Do(task)
	s.StartAsync()
	s.RemoveByID(j)
	fmt.Println(s.Len())
}
Output:

1

func (*Scheduler) RemoveByReference

func (s *Scheduler) RemoveByReference(job *Job)

RemoveByReference removes specific Job by reference

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	j, _ := s.Every(1).Week().Do(task)
	_, _ = s.Every(1).Week().Do(task)
	s.StartAsync()
	s.RemoveByReference(j)
	fmt.Println(s.Len())
}
Output:

1

func (*Scheduler) RemoveByTag

func (s *Scheduler) RemoveByTag(tag string) error

RemoveByTag will remove jobs that match the given tag.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Week().Tag("tag1").Do(task)
	_, _ = s.Every(1).Week().Tag("tag2").Do(task)
	s.StartAsync()
	_ = s.RemoveByTag("tag1")
	fmt.Println(s.Len())
}
Output:

1

func (*Scheduler) RemoveByTags

func (s *Scheduler) RemoveByTags(tags ...string) error

RemoveByTags will remove jobs that match all given tags.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Week().Tag("tag1", "tag2", "tag3").Do(task)
	_, _ = s.Every(1).Week().Tag("tag1", "tag2").Do(task)
	_, _ = s.Every(1).Week().Tag("tag1").Do(task)
	s.StartAsync()
	_ = s.RemoveByTags("tag1", "tag2")
	fmt.Println(s.Len())
}
Output:

1

func (*Scheduler) RemoveByTagsAny

func (s *Scheduler) RemoveByTagsAny(tags ...string) error

RemoveByTagsAny will remove jobs that match any one of the given tags.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Week().Tag("tag1", "tag2", "tag3").Do(task)
	_, _ = s.Every(1).Week().Tag("tag1").Do(task)
	_, _ = s.Every(1).Week().Tag("tag2").Do(task)
	_, _ = s.Every(1).Week().Tag("tag3").Do(task)
	s.StartAsync()
	_ = s.RemoveByTagsAny("tag1", "tag3")
	fmt.Println(s.Len())
}
Output:

1

func (*Scheduler) RunAll

func (s *Scheduler) RunAll()

RunAll run all Jobs regardless if they are scheduled to run or not

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().At("10:00").Do(task)
	_, _ = s.Every(2).Day().At("10:00").Do(task)
	_, _ = s.Every(3).Day().At("10:00").Do(task)
	s.StartAsync()
	s.RunAll()
}
Output:

func (*Scheduler) RunAllWithDelay

func (s *Scheduler) RunAllWithDelay(d time.Duration)

RunAllWithDelay runs all Jobs with the provided delay in between each Job

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().At("10:00").Do(task)
	_, _ = s.Every(2).Day().At("10:00").Do(task)
	_, _ = s.Every(3).Day().At("10:00").Do(task)
	s.StartAsync()
	s.RunAllWithDelay(10 * time.Second)
}
Output:

func (*Scheduler) RunByTag

func (s *Scheduler) RunByTag(tag string) error

RunByTag runs all the Jobs containing a specific tag regardless of whether they are scheduled to run or not

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().At("10:00").Do(task)
	_, _ = s.Every(2).Day().Tag("tag").At("10:00").Do(task)
	s.StartAsync()
	_ = s.RunByTag("tag")
}
Output:

func (*Scheduler) RunByTagWithDelay

func (s *Scheduler) RunByTagWithDelay(tag string, d time.Duration) error

RunByTagWithDelay is same as RunByTag but introduces a delay between each Job execution

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Day().Tag("tag").At("10:00").Do(task)
	_, _ = s.Every(2).Day().Tag("tag").At("10:00").Do(task)
	s.StartAsync()
	_ = s.RunByTagWithDelay("tag", 2*time.Second)
}
Output:

func (*Scheduler) Saturday

func (s *Scheduler) Saturday() *Scheduler

Saturday sets the start day as Saturday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Saturday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Saturday

func (*Scheduler) Second

func (s *Scheduler) Second() *Scheduler

Second sets the unit with seconds

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// the default unit is seconds
	// these are all the same
	_, _ = s.Every(1).Do(task)
	_, _ = s.Every(1).Second().Do(task)
	_, _ = s.Every(1).Seconds().Do(task)
	_, _ = s.Every("1s").Seconds().Do(task)
	_, _ = s.Every(time.Second).Seconds().Do(task)
}
Output:

func (*Scheduler) Seconds

func (s *Scheduler) Seconds() *Scheduler

Seconds sets the unit with seconds

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// the default unit is seconds
	// these are all the same
	_, _ = s.Every(1).Do(task)
	_, _ = s.Every(1).Second().Do(task)
	_, _ = s.Every(1).Seconds().Do(task)
	_, _ = s.Every("1s").Seconds().Do(task)
	_, _ = s.Every(time.Second).Seconds().Do(task)
}
Output:

func (*Scheduler) SetMaxConcurrentJobs

func (s *Scheduler) SetMaxConcurrentJobs(n int, mode limitMode)

SetMaxConcurrentJobs limits how many jobs can be running at the same time. This is useful when running resource intensive jobs and a precise start time is not critical.

Note: WaitMode and RescheduleMode provide details on usage and potential risks.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.SetMaxConcurrentJobs(1, gocron.RescheduleMode)
	_, _ = s.Every(1).Seconds().Do(func() {
		fmt.Println("This will run once every 5 seconds even though it is scheduled every second because maximum concurrent job limit is set.")
		time.Sleep(5 * time.Second)
	})
}
Output:

func (*Scheduler) SingletonMode

func (s *Scheduler) SingletonMode() *Scheduler

SingletonMode prevents a new job from starting if the prior job has not yet completed its run

Warning: do not use this mode if your jobs will continue to stack up beyond the ability of the limit workers to keep up. An example of what NOT to do:

 s.Every("1s").SingletonMode().Do(func() {
     // this will result in an ever-growing number of goroutines
	   // blocked trying to send to the buffered channel
     time.Sleep(10 * time.Minute)
 })
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Second().SingletonMode().Do(task)
}
Output:

func (*Scheduler) SingletonModeAll

func (s *Scheduler) SingletonModeAll()

SingletonModeAll prevents new jobs from starting if the prior instance of the particular job has not yet completed its run

Warning: do not use this mode if your jobs will continue to stack up beyond the ability of the limit workers to keep up. An example of what NOT to do:

 s := gocron.NewScheduler(time.UTC)
 s.SingletonModeAll()

 s.Every("1s").Do(func() {
     // this will result in an ever-growing number of goroutines
	   // blocked trying to send to the buffered channel
     time.Sleep(10 * time.Minute)
 })
Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.SingletonModeAll()

	_, _ = s.Every(1).Second().Do(task)
}
Output:

func (*Scheduler) StartAsync

func (s *Scheduler) StartAsync()

StartAsync starts all jobs without blocking the current thread

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(3).Seconds().Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) StartAt

func (s *Scheduler) StartAt(t time.Time) *Scheduler

StartAt schedules the next run of the Job. If this time is in the past, the configured interval will be used to calculate the next future time

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	specificTime := time.Date(2019, time.November, 10, 15, 0, 0, 0, time.UTC)
	_, _ = s.Every(1).Hour().StartAt(specificTime).Do(task)
	s.StartBlocking()
}
Output:

func (*Scheduler) StartBlocking

func (s *Scheduler) StartBlocking()

StartBlocking starts all jobs and blocks the current thread. This blocking method can be stopped with Stop() from a separate goroutine.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(3).Seconds().Do(task)
	s.StartBlocking()
}
Output:

func (*Scheduler) StartImmediately

func (s *Scheduler) StartImmediately() *Scheduler

StartImmediately sets the job to run immediately upon starting the scheduler or adding the job to a running scheduler. This overrides the jobs start status of any previously called methods in the chain.

Note: This is the default behavior of the scheduler for most jobs, but is useful for overriding the default behavior of Cron scheduled jobs which default to WaitForSchedule.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Cron("0 0 * * 6,0").StartImmediately().Do(task)
	s.StartBlocking()
}
Output:

func (*Scheduler) Stop

func (s *Scheduler) Stop()

Stop stops the scheduler. This is a no-op if the scheduler is already stopped. It waits for all running jobs to finish before returning, so it is safe to assume that running jobs will finish when calling this.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	_, _ = s.Every(1).Second().Do(task)
	s.StartAsync()
	s.Stop()
	fmt.Println(s.IsRunning())

	s = gocron.NewScheduler(time.UTC)

	go func() {
		time.Sleep(1 * time.Second)
		s.Stop()
	}()

	s.StartBlocking()
	fmt.Println(".Stop() stops the blocking start")

}
Output:

false
.Stop() stops the blocking start

func (*Scheduler) StopBlockingChan

func (s *Scheduler) StopBlockingChan()

func (*Scheduler) Sunday

func (s *Scheduler) Sunday() *Scheduler

Sunday sets the start day as Sunday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Sunday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Sunday

func (*Scheduler) Tag

func (s *Scheduler) Tag(t ...string) *Scheduler

Tag will add a tag when creating a job.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	j, _ := s.Every(1).Week().Tag("tag").Do(task)
	fmt.Println(j.Tags())
}
Output:

[tag]

func (*Scheduler) TagsUnique

func (s *Scheduler) TagsUnique()

TagsUnique forces job tags to be unique across the scheduler when adding tags with (s *Scheduler) Tag(). This does not enforce uniqueness on tags added via (j *Job) Tag()

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.TagsUnique()

	_, _ = s.Every(1).Week().Tag("foo").Do(task)
	_, err := s.Every(1).Week().Tag("foo").Do(task)

	fmt.Println(err)
}
Output:

gocron: a non-unique tag was set on the job: foo

func (*Scheduler) TaskPresent

func (s *Scheduler) TaskPresent(j interface{}) bool

TaskPresent checks if specific job's function was added to the scheduler.

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Do(task)
	fmt.Println(s.TaskPresent(task))
}
Output:

true

func (*Scheduler) Thursday

func (s *Scheduler) Thursday() *Scheduler

Thursday sets the start day as Thursday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Thursday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Thursday

func (*Scheduler) Tuesday

func (s *Scheduler) Tuesday() *Scheduler

Tuesday sets the start day as Tuesday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Tuesday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Tuesday

func (*Scheduler) Update

func (s *Scheduler) Update() (*Job, error)

Update stops the job (if running) and starts it with any updates that were made to the job in the scheduler chain. Job() must be called first to put the given job in focus.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every("1s").Do(task)
	s.StartAsync()

	time.Sleep(10 * time.Second)
	_, _ = s.Job(j).Every("10m").Update()

	time.Sleep(30 * time.Minute)
	_, _ = s.Job(j).Every(1).Day().At("02:00").Update()
}
Output:

func (*Scheduler) WaitForSchedule

func (s *Scheduler) WaitForSchedule() *Scheduler

WaitForSchedule sets the job to not start immediately but rather wait until the first scheduled interval.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	// job will run 5 minutes from the scheduler starting
	_, _ = s.Every("5m").WaitForSchedule().Do(task)

	// job will run immediately and 5 minutes from the scheduler starting
	_, _ = s.Every("5m").Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) WaitForScheduleAll

func (s *Scheduler) WaitForScheduleAll()

WaitForScheduleAll defaults the scheduler to create all new jobs with the WaitForSchedule option as true. The jobs will not start immediately but rather will wait until their first scheduled interval.

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	s.WaitForScheduleAll()

	// all jobs will run 5 minutes from the scheduler starting
	_, _ = s.Every("5m").Do(task)
	_, _ = s.Every("5m").Do(task)
	s.StartAsync()
}
Output:

func (*Scheduler) Wednesday

func (s *Scheduler) Wednesday() *Scheduler

Wednesday sets the start day as Wednesday

Example
package main

import (
	"fmt"
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)
	j, _ := s.Every(1).Day().Wednesday().Do(task)
	s.StartAsync()
	wd, _ := j.Weekday()
	fmt.Println(wd)
}
Output:

Wednesday

func (*Scheduler) Week

func (s *Scheduler) Week() *Scheduler

Week sets the unit with weeks

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Week().Do(task)
	_, _ = s.Every(1).Weeks().Do(task)

	_, _ = s.Every(1).Week().Monday().Wednesday().Friday().Do(task)
}
Output:

func (*Scheduler) Weekday

func (s *Scheduler) Weekday(weekDay time.Weekday) *Scheduler

Weekday sets the scheduledWeekdays with a specifics weekdays

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Week().Weekday(time.Monday).Do(task)
	_, _ = s.Every(1).Weeks().Weekday(time.Tuesday).Weekday(time.Friday).Do(task)
}
Output:

func (*Scheduler) Weeks

func (s *Scheduler) Weeks() *Scheduler

Weeks sets the unit with weeks

Example
package main

import (
	"time"

	"github.com/andoma-go/gocron"
)

var task = func() {}

func main() {
	s := gocron.NewScheduler(time.UTC)

	_, _ = s.Every(1).Week().Do(task)
	_, _ = s.Every(1).Weeks().Do(task)

	_, _ = s.Every(2).Weeks().Monday().Wednesday().Friday().Do(task)
}
Output:

func (*Scheduler) WithDistributedElector

func (s *Scheduler) WithDistributedElector(e Elector)

WithDistributedElector prevents the same job from being run more than once when multiple schedulers are trying to schedule the same job, by allowing only the leader to run jobs. Non-leaders wait until the leader instance goes down and then a new leader is elected.

Compared with the distributed lock, the election is the same as leader/follower framework. All jobs are only scheduled and execute on the leader scheduler instance. Only when the leader scheduler goes down and one of the scheduler instances is successfully elected, then the new leader scheduler instance can schedule jobs.

func (*Scheduler) WithDistributedLocker

func (s *Scheduler) WithDistributedLocker(l Locker)

WithDistributedLocker prevents the same job from being run more than once when multiple schedulers are trying to schedule the same job.

One strategy to reduce splay in the job execution times when using intervals (e.g. 1s, 1m, 1h), on each scheduler instance, is to use StartAt with time.Now().Round(interval) to start the job at the next interval boundary.

Another strategy is to use the Cron or CronWithSeconds methods as they use the same behavior described above using StartAt.

NOTE - the Locker will NOT lock jobs using the singleton options: SingletonMode, or SingletonModeAll

NOTE - beware of potential race conditions when running the Locker with SetMaxConcurrentJobs and WaitMode as jobs are not guaranteed to be locked when each scheduler's is below its limit and able to run the job.

Example
package main

import ()

func main() {
	//redisOptions := &redis.Options{
	//	Addr: "localhost:6379",
	//}
	//redisClient := redis.NewClient(redisOptions)
	//locker, err := redislock.NewRedisLocker(redisClient)
	//if err != nil {
	//	// handle the error
	//}
	//
	//s := gocron.NewScheduler(time.UTC)
	//s.WithDistributedLocker(locker)
	//_, err = s.Every("500ms").Do(task)
	//if err != nil {
	//	// handle the error
	//}
}
Output:

type TimeWrapper

type TimeWrapper interface {
	Now(*time.Location) time.Time
	Unix(int64, int64) time.Time
	Sleep(time.Duration)
}

TimeWrapper is an interface that wraps the Now, Sleep, and Unix methods of the time package. This allows the library and users to mock the time package for testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL