spec.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package cron
  2. import "time"
  3. // SpecSchedule specifies a duty cycle (to the second granularity), based on a
  4. // traditional crontab specification. It is computed initially and stored as bit sets.
  5. type SpecSchedule struct {
  6. Second, Minute, Hour, Dom, Month, Dow uint64
  7. }
  8. // bounds provides a range of acceptable values (plus a map of name to value).
  9. type bounds struct {
  10. min, max uint
  11. names map[string]uint
  12. }
  13. // The bounds for each field.
  14. var (
  15. seconds = bounds{0, 59, nil}
  16. minutes = bounds{0, 59, nil}
  17. hours = bounds{0, 23, nil}
  18. dom = bounds{1, 31, nil}
  19. months = bounds{1, 12, map[string]uint{
  20. "jan": 1,
  21. "feb": 2,
  22. "mar": 3,
  23. "apr": 4,
  24. "may": 5,
  25. "jun": 6,
  26. "jul": 7,
  27. "aug": 8,
  28. "sep": 9,
  29. "oct": 10,
  30. "nov": 11,
  31. "dec": 12,
  32. }}
  33. dow = bounds{0, 6, map[string]uint{
  34. "sun": 0,
  35. "mon": 1,
  36. "tue": 2,
  37. "wed": 3,
  38. "thu": 4,
  39. "fri": 5,
  40. "sat": 6,
  41. }}
  42. )
  43. const (
  44. // Set the top bit if a star was included in the expression.
  45. starBit = 1 << 63
  46. )
  47. // Next returns the next time this schedule is activated, greater than the given
  48. // time. If no time can be found to satisfy the schedule, return the zero time.
  49. func (s *SpecSchedule) Next(t time.Time) time.Time {
  50. // General approach:
  51. // For Month, Day, Hour, Minute, Second:
  52. // Check if the time value matches. If yes, continue to the next field.
  53. // If the field doesn't match the schedule, then increment the field until it matches.
  54. // While incrementing the field, a wrap-around brings it back to the beginning
  55. // of the field list (since it is necessary to re-verify previous field
  56. // values)
  57. // Start at the earliest possible time (the upcoming second).
  58. t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
  59. // This flag indicates whether a field has been incremented.
  60. added := false
  61. // If no time is found within five years, return zero.
  62. yearLimit := t.Year() + 5
  63. WRAP:
  64. if t.Year() > yearLimit {
  65. return time.Time{}
  66. }
  67. // Find the first applicable month.
  68. // If it's this month, then do nothing.
  69. for 1<<uint(t.Month())&s.Month == 0 {
  70. // If we have to add a month, reset the other parts to 0.
  71. if !added {
  72. added = true
  73. // Otherwise, set the date at the beginning (since the current time is irrelevant).
  74. t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
  75. }
  76. t = t.AddDate(0, 1, 0)
  77. // Wrapped around.
  78. if t.Month() == time.January {
  79. goto WRAP
  80. }
  81. }
  82. // Now get a day in that month.
  83. for !dayMatches(s, t) {
  84. if !added {
  85. added = true
  86. t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
  87. }
  88. t = t.AddDate(0, 0, 1)
  89. if t.Day() == 1 {
  90. goto WRAP
  91. }
  92. }
  93. for 1<<uint(t.Hour())&s.Hour == 0 {
  94. if !added {
  95. added = true
  96. t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
  97. }
  98. t = t.Add(1 * time.Hour)
  99. if t.Hour() == 0 {
  100. goto WRAP
  101. }
  102. }
  103. for 1<<uint(t.Minute())&s.Minute == 0 {
  104. if !added {
  105. added = true
  106. t = t.Truncate(time.Minute)
  107. }
  108. t = t.Add(1 * time.Minute)
  109. if t.Minute() == 0 {
  110. goto WRAP
  111. }
  112. }
  113. for 1<<uint(t.Second())&s.Second == 0 {
  114. if !added {
  115. added = true
  116. t = t.Truncate(time.Second)
  117. }
  118. t = t.Add(1 * time.Second)
  119. if t.Second() == 0 {
  120. goto WRAP
  121. }
  122. }
  123. return t
  124. }
  125. // dayMatches returns true if the schedule's day-of-week and day-of-month
  126. // restrictions are satisfied by the given time.
  127. func dayMatches(s *SpecSchedule, t time.Time) bool {
  128. var (
  129. domMatch bool = 1<<uint(t.Day())&s.Dom > 0
  130. dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
  131. )
  132. if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
  133. return domMatch && dowMatch
  134. }
  135. return domMatch || dowMatch
  136. }