spec_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. //nolint:all
  2. package cron
  3. import (
  4. "strings"
  5. "testing"
  6. "time"
  7. )
  8. func TestActivation(t *testing.T) {
  9. tests := []struct {
  10. time, spec string
  11. expected bool
  12. }{
  13. // Every fifteen minutes.
  14. {"Mon Jul 9 15:00 2012", "0/15 * * * *", true},
  15. {"Mon Jul 9 15:45 2012", "0/15 * * * *", true},
  16. {"Mon Jul 9 15:40 2012", "0/15 * * * *", false},
  17. // Every fifteen minutes, starting at 5 minutes.
  18. {"Mon Jul 9 15:05 2012", "5/15 * * * *", true},
  19. {"Mon Jul 9 15:20 2012", "5/15 * * * *", true},
  20. {"Mon Jul 9 15:50 2012", "5/15 * * * *", true},
  21. // Named months
  22. {"Sun Jul 15 15:00 2012", "0/15 * * Jul *", true},
  23. {"Sun Jul 15 15:00 2012", "0/15 * * Jun *", false},
  24. // Everything set.
  25. {"Sun Jul 15 08:30 2012", "30 08 ? Jul Sun", true},
  26. {"Sun Jul 15 08:30 2012", "30 08 15 Jul ?", true},
  27. {"Mon Jul 16 08:30 2012", "30 08 ? Jul Sun", false},
  28. {"Mon Jul 16 08:30 2012", "30 08 15 Jul ?", false},
  29. // Predefined schedules
  30. {"Mon Jul 9 15:00 2012", "@hourly", true},
  31. {"Mon Jul 9 15:04 2012", "@hourly", false},
  32. {"Mon Jul 9 15:00 2012", "@daily", false},
  33. {"Mon Jul 9 00:00 2012", "@daily", true},
  34. {"Mon Jul 9 00:00 2012", "@weekly", false},
  35. {"Sun Jul 8 00:00 2012", "@weekly", true},
  36. {"Sun Jul 8 01:00 2012", "@weekly", false},
  37. {"Sun Jul 8 00:00 2012", "@monthly", false},
  38. {"Sun Jul 1 00:00 2012", "@monthly", true},
  39. // Test interaction of DOW and DOM.
  40. // If both are restricted, then only one needs to match.
  41. {"Sun Jul 15 00:00 2012", "* * 1,15 * Sun", true},
  42. {"Fri Jun 15 00:00 2012", "* * 1,15 * Sun", true},
  43. {"Wed Aug 1 00:00 2012", "* * 1,15 * Sun", true},
  44. {"Sun Jul 15 00:00 2012", "* * */10 * Sun", true}, // verifies #70
  45. // However, if one has a star, then both need to match.
  46. {"Sun Jul 15 00:00 2012", "* * * * Mon", false},
  47. {"Mon Jul 9 00:00 2012", "* * 1,15 * *", false},
  48. {"Sun Jul 15 00:00 2012", "* * 1,15 * *", true},
  49. {"Sun Jul 15 00:00 2012", "* * */2 * Sun", true},
  50. }
  51. for _, test := range tests {
  52. sched, err := ParseStandard(test.spec)
  53. if err != nil {
  54. t.Error(err)
  55. continue
  56. }
  57. actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
  58. expected := getTime(test.time)
  59. if test.expected && expected != actual || !test.expected && expected == actual {
  60. t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
  61. test.spec, test.time, expected, actual)
  62. }
  63. }
  64. }
  65. func TestNext(t *testing.T) {
  66. runs := []struct {
  67. time, spec string
  68. expected string
  69. }{
  70. // Simple cases
  71. {"Mon Jul 9 14:45 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
  72. {"Mon Jul 9 14:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
  73. {"Mon Jul 9 14:59:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"},
  74. // Wrap around hours
  75. {"Mon Jul 9 15:45 2012", "0 20-35/15 * * * *", "Mon Jul 9 16:20 2012"},
  76. // Wrap around days
  77. {"Mon Jul 9 23:46 2012", "0 */15 * * * *", "Tue Jul 10 00:00 2012"},
  78. {"Mon Jul 9 23:45 2012", "0 20-35/15 * * * *", "Tue Jul 10 00:20 2012"},
  79. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * * *", "Tue Jul 10 00:20:15 2012"},
  80. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * * *", "Tue Jul 10 01:20:15 2012"},
  81. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * * *", "Tue Jul 10 10:20:15 2012"},
  82. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
  83. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
  84. {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
  85. // Wrap around months
  86. {"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
  87. {"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Tue Aug 1 00:00 2012"},
  88. {"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
  89. // Wrap around years
  90. {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
  91. {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
  92. // Wrap around minute, hour, day, month, and year
  93. {"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
  94. // Leap year
  95. {"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
  96. // Daylight savings time 2am EST (-5) -> 3am EDT (-4)
  97. {"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
  98. // hourly job
  99. {"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T01:00:00-0500"},
  100. {"2012-03-11T01:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T03:00:00-0400"},
  101. {"2012-03-11T03:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T04:00:00-0400"},
  102. {"2012-03-11T04:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-03-11T05:00:00-0400"},
  103. // hourly job using CRON_TZ
  104. {"2012-03-11T00:00:00-0500", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T01:00:00-0500"},
  105. {"2012-03-11T01:00:00-0500", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T03:00:00-0400"},
  106. {"2012-03-11T03:00:00-0400", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T04:00:00-0400"},
  107. {"2012-03-11T04:00:00-0400", "CRON_TZ=America/New_York 0 0 * * * ?", "2012-03-11T05:00:00-0400"},
  108. // 1am nightly job
  109. {"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-03-11T01:00:00-0500"},
  110. {"2012-03-11T01:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-03-12T01:00:00-0400"},
  111. // 2am nightly job (skipped)
  112. {"2012-03-11T00:00:00-0500", "TZ=America/New_York 0 0 2 * * ?", "2012-03-12T02:00:00-0400"},
  113. // Daylight savings time 2am EDT (-4) => 1am EST (-5)
  114. {"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
  115. {"2012-11-04T01:45:00-0400", "TZ=America/New_York 0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
  116. // hourly job
  117. {"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T01:00:00-0400"},
  118. {"2012-11-04T01:00:00-0400", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T01:00:00-0500"},
  119. {"2012-11-04T01:00:00-0500", "TZ=America/New_York 0 0 * * * ?", "2012-11-04T02:00:00-0500"},
  120. // 1am nightly job (runs twice)
  121. {"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
  122. {"2012-11-04T01:00:00-0400", "TZ=America/New_York 0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
  123. {"2012-11-04T01:00:00-0500", "TZ=America/New_York 0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
  124. // 2am nightly job
  125. {"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
  126. {"2012-11-04T02:00:00-0500", "TZ=America/New_York 0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
  127. // 3am nightly job
  128. {"2012-11-04T00:00:00-0400", "TZ=America/New_York 0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
  129. {"2012-11-04T03:00:00-0500", "TZ=America/New_York 0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
  130. // hourly job
  131. {"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0400"},
  132. {"TZ=America/New_York 2012-11-04T01:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0500"},
  133. {"TZ=America/New_York 2012-11-04T01:00:00-0500", "0 0 * * * ?", "2012-11-04T02:00:00-0500"},
  134. // 1am nightly job (runs twice)
  135. {"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
  136. {"TZ=America/New_York 2012-11-04T01:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
  137. {"TZ=America/New_York 2012-11-04T01:00:00-0500", "0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
  138. // 2am nightly job
  139. {"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
  140. {"TZ=America/New_York 2012-11-04T02:00:00-0500", "0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
  141. // 3am nightly job
  142. {"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
  143. {"TZ=America/New_York 2012-11-04T03:00:00-0500", "0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
  144. // Unsatisfiable
  145. {"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
  146. {"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
  147. // Monthly job
  148. {"TZ=America/New_York 2012-11-04T00:00:00-0400", "0 0 3 3 * ?", "2012-12-03T03:00:00-0500"},
  149. // Test the scenario of DST resulting in midnight not being a valid time.
  150. // https://github.com/robfig/cron/issues/157
  151. {"2018-10-17T05:00:00-0400", "TZ=America/Sao_Paulo 0 0 9 10 * ?", "2018-11-10T06:00:00-0500"},
  152. {"2018-02-14T05:00:00-0500", "TZ=America/Sao_Paulo 0 0 9 22 * ?", "2018-02-22T07:00:00-0500"},
  153. }
  154. for _, c := range runs {
  155. sched, err := secondParser.Parse(c.spec)
  156. if err != nil {
  157. t.Error(err)
  158. continue
  159. }
  160. actual := sched.Next(getTime(c.time))
  161. expected := getTime(c.expected)
  162. if !actual.Equal(expected) {
  163. t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
  164. }
  165. }
  166. }
  167. func TestErrors(t *testing.T) {
  168. invalidSpecs := []string{
  169. "xyz",
  170. "60 0 * * *",
  171. "0 60 * * *",
  172. "0 0 * * XYZ",
  173. }
  174. for _, spec := range invalidSpecs {
  175. _, err := ParseStandard(spec)
  176. if err == nil {
  177. t.Error("expected an error parsing: ", spec)
  178. }
  179. }
  180. }
  181. func getTime(value string) time.Time {
  182. if value == "" {
  183. return time.Time{}
  184. }
  185. var location = time.Local
  186. if strings.HasPrefix(value, "TZ=") {
  187. parts := strings.Fields(value)
  188. loc, err := time.LoadLocation(parts[0][len("TZ="):])
  189. if err != nil {
  190. panic("could not parse location:" + err.Error())
  191. }
  192. location = loc
  193. value = parts[1]
  194. }
  195. var layouts = []string{
  196. "Mon Jan 2 15:04 2006",
  197. "Mon Jan 2 15:04:05 2006",
  198. }
  199. for _, layout := range layouts {
  200. if t, err := time.ParseInLocation(layout, value, location); err == nil {
  201. return t
  202. }
  203. }
  204. if t, err := time.ParseInLocation("2006-01-02T15:04:05-0700", value, location); err == nil {
  205. return t
  206. }
  207. panic("could not parse time value " + value)
  208. }
  209. func TestNextWithTz(t *testing.T) {
  210. runs := []struct {
  211. time, spec string
  212. expected string
  213. }{
  214. // Failing tests
  215. {"2016-01-03T13:09:03+0530", "14 14 * * *", "2016-01-03T14:14:00+0530"},
  216. {"2016-01-03T04:09:03+0530", "14 14 * * ?", "2016-01-03T14:14:00+0530"},
  217. // Passing tests
  218. {"2016-01-03T14:09:03+0530", "14 14 * * *", "2016-01-03T14:14:00+0530"},
  219. {"2016-01-03T14:00:00+0530", "14 14 * * ?", "2016-01-03T14:14:00+0530"},
  220. }
  221. for _, c := range runs {
  222. sched, err := ParseStandard(c.spec)
  223. if err != nil {
  224. t.Error(err)
  225. continue
  226. }
  227. actual := sched.Next(getTimeTZ(c.time))
  228. expected := getTimeTZ(c.expected)
  229. if !actual.Equal(expected) {
  230. t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
  231. }
  232. }
  233. }
  234. func getTimeTZ(value string) time.Time {
  235. if value == "" {
  236. return time.Time{}
  237. }
  238. t, err := time.Parse("Mon Jan 2 15:04 2006", value)
  239. if err != nil {
  240. t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
  241. if err != nil {
  242. t, err = time.Parse("2006-01-02T15:04:05-0700", value)
  243. if err != nil {
  244. panic(err)
  245. }
  246. }
  247. }
  248. return t
  249. }
  250. // https://github.com/robfig/cron/issues/144
  251. func TestSlash0NoHang(t *testing.T) {
  252. schedule := "TZ=America/New_York 15/0 * * * *"
  253. _, err := ParseStandard(schedule)
  254. if err == nil {
  255. t.Error("expected an error on 0 increment")
  256. }
  257. }