cron.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // Package cron implements a crontab-like service to execute and schedule repeative tasks/jobs.
  2. package cron
  3. // Example:
  4. //
  5. // c := cron.New()
  6. // c.MustAdd("dailyReport", "0 0 * * *", func() { ... })
  7. // c.Start()
  8. import (
  9. "sync"
  10. "time"
  11. "github.com/pkg/errors"
  12. )
  13. type job struct {
  14. schedule *Schedule
  15. run func()
  16. }
  17. // Cron is a crontab-like struct for tasks/jobs scheduling.
  18. type Cron struct {
  19. sync.RWMutex
  20. interval time.Duration
  21. timezone *time.Location
  22. ticker *time.Ticker
  23. jobs map[string]*job
  24. }
  25. // New create a new Cron struct with default tick interval of 1 minute
  26. // and timezone in UTC.
  27. //
  28. // You can change the default tick interval with Cron.SetInterval().
  29. // You can change the default timezone with Cron.SetTimezone().
  30. func New() *Cron {
  31. return &Cron{
  32. interval: 1 * time.Minute,
  33. timezone: time.UTC,
  34. jobs: map[string]*job{},
  35. }
  36. }
  37. // SetInterval changes the current cron tick interval
  38. // (it usually should be >= 1 minute).
  39. func (c *Cron) SetInterval(d time.Duration) {
  40. // update interval
  41. c.Lock()
  42. wasStarted := c.ticker != nil
  43. c.interval = d
  44. c.Unlock()
  45. // restart the ticker
  46. if wasStarted {
  47. c.Start()
  48. }
  49. }
  50. // SetTimezone changes the current cron tick timezone.
  51. func (c *Cron) SetTimezone(l *time.Location) {
  52. c.Lock()
  53. defer c.Unlock()
  54. c.timezone = l
  55. }
  56. // MustAdd is similar to Add() but panic on failure.
  57. func (c *Cron) MustAdd(jobID string, cronExpr string, run func()) {
  58. if err := c.Add(jobID, cronExpr, run); err != nil {
  59. panic(err)
  60. }
  61. }
  62. // Add registers a single cron job.
  63. //
  64. // If there is already a job with the provided id, then the old job
  65. // will be replaced with the new one.
  66. //
  67. // cronExpr is a regular cron expression, eg. "0 */3 * * *" (aka. at minute 0 past every 3rd hour).
  68. // Check cron.NewSchedule() for the supported tokens.
  69. func (c *Cron) Add(jobID string, cronExpr string, run func()) error {
  70. if run == nil {
  71. return errors.New("failed to add new cron job: run must be non-nil function")
  72. }
  73. c.Lock()
  74. defer c.Unlock()
  75. schedule, err := NewSchedule(cronExpr)
  76. if err != nil {
  77. return errors.Wrap(err, "failed to add new cron job")
  78. }
  79. c.jobs[jobID] = &job{
  80. schedule: schedule,
  81. run: run,
  82. }
  83. return nil
  84. }
  85. // Remove removes a single cron job by its id.
  86. func (c *Cron) Remove(jobID string) {
  87. c.Lock()
  88. defer c.Unlock()
  89. delete(c.jobs, jobID)
  90. }
  91. // RemoveAll removes all registered cron jobs.
  92. func (c *Cron) RemoveAll() {
  93. c.Lock()
  94. defer c.Unlock()
  95. c.jobs = map[string]*job{}
  96. }
  97. // Total returns the current total number of registered cron jobs.
  98. func (c *Cron) Total() int {
  99. c.RLock()
  100. defer c.RUnlock()
  101. return len(c.jobs)
  102. }
  103. // Stop stops the current cron ticker (if not already).
  104. //
  105. // You can resume the ticker by calling Start().
  106. func (c *Cron) Stop() {
  107. c.Lock()
  108. defer c.Unlock()
  109. if c.ticker == nil {
  110. return // already stopped
  111. }
  112. c.ticker.Stop()
  113. c.ticker = nil
  114. }
  115. // Start starts the cron ticker.
  116. //
  117. // Calling Start() on already started cron will restart the ticker.
  118. func (c *Cron) Start() {
  119. c.Stop()
  120. c.Lock()
  121. c.ticker = time.NewTicker(c.interval)
  122. c.Unlock()
  123. go func() {
  124. for t := range c.ticker.C {
  125. c.runDue(t)
  126. }
  127. }()
  128. }
  129. // HasStarted checks whether the current Cron ticker has been started.
  130. func (c *Cron) HasStarted() bool {
  131. c.RLock()
  132. defer c.RUnlock()
  133. return c.ticker != nil
  134. }
  135. // runDue runs all registered jobs that are scheduled for the provided time.
  136. func (c *Cron) runDue(t time.Time) {
  137. c.RLock()
  138. defer c.RUnlock()
  139. moment := NewMoment(t.In(c.timezone))
  140. for _, j := range c.jobs {
  141. if j.schedule.IsDue(moment) {
  142. go j.run()
  143. }
  144. }
  145. }