123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- // Package cron implements a crontab-like service to execute and schedule repeative tasks/jobs.
- package cron
- // Example:
- //
- // c := cron.New()
- // c.MustAdd("dailyReport", "0 0 * * *", func() { ... })
- // c.Start()
- import (
- "sync"
- "time"
- "github.com/pkg/errors"
- )
- type job struct {
- schedule *Schedule
- run func()
- }
- // Cron is a crontab-like struct for tasks/jobs scheduling.
- type Cron struct {
- sync.RWMutex
- interval time.Duration
- timezone *time.Location
- ticker *time.Ticker
- jobs map[string]*job
- }
- // New create a new Cron struct with default tick interval of 1 minute
- // and timezone in UTC.
- //
- // You can change the default tick interval with Cron.SetInterval().
- // You can change the default timezone with Cron.SetTimezone().
- func New() *Cron {
- return &Cron{
- interval: 1 * time.Minute,
- timezone: time.UTC,
- jobs: map[string]*job{},
- }
- }
- // SetInterval changes the current cron tick interval
- // (it usually should be >= 1 minute).
- func (c *Cron) SetInterval(d time.Duration) {
- // update interval
- c.Lock()
- wasStarted := c.ticker != nil
- c.interval = d
- c.Unlock()
- // restart the ticker
- if wasStarted {
- c.Start()
- }
- }
- // SetTimezone changes the current cron tick timezone.
- func (c *Cron) SetTimezone(l *time.Location) {
- c.Lock()
- defer c.Unlock()
- c.timezone = l
- }
- // MustAdd is similar to Add() but panic on failure.
- func (c *Cron) MustAdd(jobID string, cronExpr string, run func()) {
- if err := c.Add(jobID, cronExpr, run); err != nil {
- panic(err)
- }
- }
- // Add registers a single cron job.
- //
- // If there is already a job with the provided id, then the old job
- // will be replaced with the new one.
- //
- // cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
- // Check cron.NewSchedule() for the supported tokens.
- func (c *Cron) Add(jobID string, cronExpr string, run func()) error {
- if run == nil {
- return errors.New("failed to add new cron job: run must be non-nil function")
- }
- c.Lock()
- defer c.Unlock()
- schedule, err := NewSchedule(cronExpr)
- if err != nil {
- return errors.Wrap(err, "failed to add new cron job")
- }
- c.jobs[jobID] = &job{
- schedule: schedule,
- run: run,
- }
- return nil
- }
- // Remove removes a single cron job by its id.
- func (c *Cron) Remove(jobID string) {
- c.Lock()
- defer c.Unlock()
- delete(c.jobs, jobID)
- }
- // RemoveAll removes all registered cron jobs.
- func (c *Cron) RemoveAll() {
- c.Lock()
- defer c.Unlock()
- c.jobs = map[string]*job{}
- }
- // Total returns the current total number of registered cron jobs.
- func (c *Cron) Total() int {
- c.RLock()
- defer c.RUnlock()
- return len(c.jobs)
- }
- // Stop stops the current cron ticker (if not already).
- //
- // You can resume the ticker by calling Start().
- func (c *Cron) Stop() {
- c.Lock()
- defer c.Unlock()
- if c.ticker == nil {
- return // already stopped
- }
- c.ticker.Stop()
- c.ticker = nil
- }
- // Start starts the cron ticker.
- //
- // Calling Start() on already started cron will restart the ticker.
- func (c *Cron) Start() {
- c.Stop()
- c.Lock()
- c.ticker = time.NewTicker(c.interval)
- c.Unlock()
- go func() {
- for t := range c.ticker.C {
- c.runDue(t)
- }
- }()
- }
- // HasStarted checks whether the current Cron ticker has been started.
- func (c *Cron) HasStarted() bool {
- c.RLock()
- defer c.RUnlock()
- return c.ticker != nil
- }
- // runDue runs all registered jobs that are scheduled for the provided time.
- func (c *Cron) runDue(t time.Time) {
- c.RLock()
- defer c.RUnlock()
- moment := NewMoment(t.In(c.timezone))
- for _, j := range c.jobs {
- if j.schedule.IsDue(moment) {
- go j.run()
- }
- }
- }
|