// 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() } } }