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