@@ -199,6 +199,134 @@ func TestNext(t *testing.T) {
199199 }
200200}
201201
202+ func TestPrev (t * testing.T ) {
203+ runs := []struct {
204+ time , spec string
205+ expected string
206+ }{
207+ // Simple cases
208+ {"Mon Jul 9 14:45 2012" , "0 0/15 * * * *" , "Mon Jul 9 14:30 2012" },
209+ {"Mon Jul 9 14:01 2012" , "0 0/15 * * * *" , "Mon Jul 9 14:00 2012" },
210+ {"Mon Jul 9 14:00:01 2012" , "0 0/15 * * * *" , "Mon Jul 9 14:00 2012" },
211+
212+ // Wrap around hours
213+ {"Mon Jul 9 15:20 2012" , "0 20-35/15 * * * *" , "Mon Jul 9 14:35 2012" },
214+
215+ // Wrap around days
216+ {"Mon Jul 9 00:00 2012" , "0 */15 * * * *" , "Sun Jul 8 23:45 2012" },
217+ {"Mon Jul 9 00:00 2012" , "0 20-35/15 * * * *" , "Sun Jul 8 23:35 2012" },
218+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 * * * *" , "Sun Jul 8 23:35:50 2012" },
219+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 0/2 * * *" , "Sun Jul 8 22:35:50 2012" },
220+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 10-12 * * *" , "Sun Jul 8 12:35:50 2012" },
221+
222+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 0/2 */2 * *" , "Sat Jul 7 22:35:50 2012" },
223+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 * 8-20 * *" , "Sun Jul 8 23:35:50 2012" },
224+ {"Mon Jul 9 00:15:51 2012" , "15/35 20-35/15 * 8-20 Jul *" , "Sun Jul 8 23:35:50 2012" },
225+
226+ // Wrap around months
227+ {"Mon Jul 9 00:15 2012" , "0 0 0 10 Apr-Oct ?" , "Thu Jun 10 00:00 2012" },
228+ {"Mon Jul 9 00:15 2012" , "0 0 0 */5 Apr,Jun Mon" , "Tue Jun 26 00:00 2012" },
229+ {"Mon Jul 9 00:15 2012" , "0 0 0 */5 Apr Mon" , "Mon Apr 30 00:00 2012" },
230+
231+ // Wrap around years
232+ {"Mon Jul 9 00:15 2012" , "0 0 0 * Aug Mon" , "Mon Aug 29 00:00 2011" },
233+ {"Mon Jul 9 00:15 2012" , "0 0 0 * Aug Mon/2" , "Wed Aug 31 00:00 2011" },
234+
235+ // Wrap around minute, hour, day, month, and year
236+ {"Sun Jan 1 00:00:00 2012" , "0 * * * * *" , "Sat Dec 31 23:59:00 2011" },
237+
238+ // Leap year
239+ {"Mon Jul 9 00:15 2011" , "0 0 0 29 Feb ?" , "Fri Feb 29 00:00 2008" },
240+
241+ // Daylight savings time 2am EST (-5) -> 3am EDT (-4)
242+ {"2012-03-11T04:00:00-0400" , "TZ=America/New_York 0 30 2 11 Mar ?" , "2011-03-11T02:30:00-0500" },
243+
244+ // hourly job
245+ {"2012-03-11T01:00:00-0500" , "TZ=America/New_York 0 0 * * * ?" , "2012-03-11T00:00:00-0500" },
246+ {"2012-03-11T03:00:00-0400" , "TZ=America/New_York 0 0 * * * ?" , "2012-03-11T01:00:00-0500" },
247+ {"2012-03-11T04:00:00-0400" , "TZ=America/New_York 0 0 * * * ?" , "2012-03-11T03:00:00-0400" },
248+ {"2012-03-11T05:00:00-0400" , "TZ=America/New_York 0 0 * * * ?" , "2012-03-11T04:00:00-0400" },
249+
250+ // hourly job using CRON_TZ
251+ {"2012-03-11T01:00:00-0500" , "CRON_TZ=America/New_York 0 0 * * * ?" , "2012-03-11T00:00:00-0500" },
252+ {"2012-03-11T03:00:00-0400" , "CRON_TZ=America/New_York 0 0 * * * ?" , "2012-03-11T01:00:00-0500" },
253+ {"2012-03-11T04:00:00-0400" , "CRON_TZ=America/New_York 0 0 * * * ?" , "2012-03-11T03:00:00-0400" },
254+ {"2012-03-11T05:00:00-0400" , "CRON_TZ=America/New_York 0 0 * * * ?" , "2012-03-11T04:00:00-0400" },
255+
256+ // 1am nightly job
257+ {"2012-03-11T04:00:00-0400" , "TZ=America/New_York 0 0 1 * * ?" , "2012-03-11T01:00:00-0500" },
258+ {"2012-03-12T04:00:00-0400" , "TZ=America/New_York 0 0 1 * * ?" , "2012-03-12T01:00:00-0400" },
259+
260+ // 2am nightly job (skipped)
261+ {"2012-03-11T04:00:00-0400" , "TZ=America/New_York 0 0 2 * * ?" , "2012-03-10T02:00:00-0500" },
262+
263+ // Daylight savings time 2am EDT (-4) => 1am EST (-5)
264+ {"2012-11-04T01:45:00-0500" , "TZ=America/New_York 0 30 1 04 Nov ?" , "2012-11-04T01:30:00-0500" },
265+ {"2012-11-04T01:45:00-0400" , "TZ=America/New_York 0 30 1 04 Nov ?" , "2012-11-04T01:30:00-0400" },
266+
267+ // hourly job
268+ {"2012-11-04T01:00:00-0400" , "TZ=America/New_York 0 0 * * * ?" , "2012-11-04T00:00:00-0400" },
269+ {"2012-11-04T01:00:00-0500" , "TZ=America/New_York 0 0 * * * ?" , "2012-11-04T01:00:00-0400" },
270+ {"2012-11-04T02:00:00-0500" , "TZ=America/New_York 0 0 * * * ?" , "2012-11-04T01:00:00-0500" },
271+
272+ // 1am nightly job (runs twice)
273+ {"2012-11-04T01:00:00-0400" , "TZ=America/New_York 0 0 1 * * ?" , "2012-11-03T01:00:00-0400" },
274+ {"2012-11-04T01:00:00-0500" , "TZ=America/New_York 0 0 1 * * ?" , "2012-11-04T01:00:00-0400" },
275+ {"2012-11-04T05:00:00-0500" , "TZ=America/New_York 0 0 1 * * ?" , "2012-11-04T01:00:00-0500" },
276+
277+ // 2am nightly job
278+ {"2012-11-04T00:00:00-0400" , "TZ=America/New_York 0 0 2 * * ?" , "2012-11-03T02:00:00-0400" },
279+ {"2012-11-04T05:00:00-0500" , "TZ=America/New_York 0 0 2 * * ?" , "2012-11-04T02:00:00-0500" },
280+
281+ // 3am nightly job
282+ {"2012-11-04T00:00:00-0400" , "TZ=America/New_York 0 0 3 * * ?" , "2012-11-03T03:00:00-0400" },
283+ {"2012-11-04T05:00:00-0500" , "TZ=America/New_York 0 0 3 * * ?" , "2012-11-04T03:00:00-0500" },
284+
285+ // hourly job
286+ {"TZ=America/New_York 2012-11-04T01:00:00-0400" , "0 0 * * * ?" , "2012-11-04T00:00:00-0400" },
287+ {"TZ=America/New_York 2012-11-04T01:00:00-0500" , "0 0 * * * ?" , "2012-11-04T01:00:00-0400" },
288+ {"TZ=America/New_York 2012-11-04T02:00:00-0500" , "0 0 * * * ?" , "2012-11-04T01:00:00-0500" },
289+
290+ // 1am nightly job (runs twice)
291+ {"TZ=America/New_York 2012-11-04T01:00:00-0400" , "0 0 1 * * ?" , "2012-11-03T01:00:00-0400" },
292+ {"TZ=America/New_York 2012-11-04T01:00:00-0500" , "0 0 1 * * ?" , "2012-11-04T01:00:00-0400" },
293+ {"TZ=America/New_York 2012-11-04T05:00:00-0500" , "0 0 1 * * ?" , "2012-11-04T01:00:00-0500" },
294+
295+ // 2am nightly job
296+ {"TZ=America/New_York 2012-11-04T00:00:00-0400" , "0 0 2 * * ?" , "2012-11-03T02:00:00-0400" },
297+ {"TZ=America/New_York 2012-11-04T05:00:00-0500" , "0 0 2 * * ?" , "2012-11-04T02:00:00-0500" },
298+
299+ // 3am nightly job
300+ {"TZ=America/New_York 2012-11-04T00:00:00-0400" , "0 0 3 * * ?" , "2012-11-03T03:00:00-0400" },
301+ {"TZ=America/New_York 2012-11-04T05:00:00-0500" , "0 0 3 * * ?" , "2012-11-04T03:00:00-0500" },
302+
303+ // Unsatisfiable
304+ {"Mon Jul 9 00:15 2012" , "0 0 0 30 Feb ?" , "" },
305+ {"Mon Jul 9 00:15 2012" , "0 0 0 31 Apr ?" , "" },
306+
307+ // Monthly job
308+ {"TZ=America/New_York 2012-12-01T00:00:00-0500" , "0 0 3 3 * ?" , "2012-11-03T03:00:00-0400" },
309+
310+ // Test the scenario of DST resulting in midnight not being a valid time.
311+ // https://github.com/robfig/cron/issues/157
312+ {"2018-12-07T05:00:00-0500" , "TZ=America/Sao_Paulo 0 0 9 10 * ?" , "2018-11-10T06:00:00-0500" },
313+ {"2018-03-14T05:00:00-0400" , "TZ=America/Sao_Paulo 0 0 9 22 * ?" , "2018-02-22T07:00:00-0500" },
314+ }
315+
316+ for _ , c := range runs {
317+ sched , err := secondParser .Parse (c .spec )
318+ if err != nil {
319+ t .Error (err )
320+ continue
321+ }
322+ actual := sched .Prev (getTime (c .time ))
323+ expected := getTime (c .expected )
324+ if ! actual .Equal (expected ) {
325+ t .Errorf ("%s, \" %s\" : (expected) %v != %v (actual)" , c .time , c .spec , expected , actual )
326+ }
327+ }
328+ }
329+
202330func TestErrors (t * testing.T ) {
203331 invalidSpecs := []string {
204332 "xyz" ,
0 commit comments