diff --git a/cronexpr.go b/cronexpr.go index 58b518f..04d28b6 100644 --- a/cronexpr.go +++ b/cronexpr.go @@ -201,13 +201,13 @@ func (expr *Expression) Next(fromTime time.Time) time.Time { return expr.nextMonth(fromTime) } if v != expr.actualDaysOfMonthList[i] { - return expr.nextDayOfMonth(fromTime) + return expr.nextActualDayOfMonth(fromTime) } // hour v = fromTime.Hour() i = sort.SearchInts(expr.hourList, v) if i == len(expr.hourList) { - return expr.nextDayOfMonth(fromTime) + return expr.nextActualDayOfMonth(fromTime) } if v != expr.hourList[i] { return expr.nextHour(fromTime) @@ -236,6 +236,87 @@ func (expr *Expression) Next(fromTime time.Time) time.Time { /******************************************************************************/ +// Last returns the closest time instant immediately before `fromTime` which +// matches the cron expression `expr`. +// +// The `time.Location` of the returned time instant is the same as that of +// `fromTime`. +// +// The zero value of time.Time is returned if no matching time instant exists +// or if a `fromTime` is itself a zero value. +func (expr *Expression) Last(fromTime time.Time) time.Time { + // Special case + if fromTime.IsZero() { + return fromTime + } + + // year + v := fromTime.Year() + i := sort.SearchInts(expr.yearList, v) + if i == 0 && v != expr.yearList[i] { + return time.Time{} + } + if i == len(expr.yearList) || v != expr.yearList[i] { + return expr.lastYear(fromTime, false) + } + // month + v = int(fromTime.Month()) + i = sort.SearchInts(expr.monthList, v) + if i == 0 && v != expr.monthList[i] { + return expr.lastYear(fromTime, true) + } + if i == len(expr.monthList) || v != expr.monthList[i] { + return expr.lastMonth(fromTime, false) + } + + expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(fromTime.Year(), int(fromTime.Month())) + if len(expr.actualDaysOfMonthList) == 0 { + return expr.lastMonth(fromTime, true) + } + + // day of month + v = fromTime.Day() + i = sort.SearchInts(expr.actualDaysOfMonthList, v) + if i == 0 && v != expr.actualDaysOfMonthList[i] { + return expr.lastMonth(fromTime, true) + } + if i == len(expr.actualDaysOfMonthList) || v != expr.actualDaysOfMonthList[i] { + return expr.lastActualDayOfMonth(fromTime, false) + } + // hour + v = fromTime.Hour() + i = sort.SearchInts(expr.hourList, v) + if i == 0 && v != expr.hourList[i] { + return expr.lastActualDayOfMonth(fromTime, true) + } + if i == len(expr.hourList) || v != expr.hourList[i] { + return expr.lastHour(fromTime, false) + } + + // minute + v = fromTime.Minute() + i = sort.SearchInts(expr.minuteList, v) + if i == 0 && v != expr.minuteList[i] { + return expr.lastHour(fromTime, true) + } + if i == len(expr.minuteList) || v != expr.minuteList[i] { + return expr.lastMinute(fromTime, false) + } + // second + v = fromTime.Second() + i = sort.SearchInts(expr.secondList, v) + if i == len(expr.secondList) { + return expr.lastMinute(fromTime, true) + } + + // If we reach this point, there is nothing better to do + // than to move to the next second + + return expr.lastSecond(fromTime) +} + +/******************************************************************************/ + // NextN returns a slice of `n` closest time instants immediately following // `fromTime` which match the cron expression `expr`. // @@ -255,7 +336,7 @@ func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time { break } nextTimes = append(nextTimes, fromTime) - n -= 1 + n-- if n == 0 { break } @@ -264,3 +345,32 @@ func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time { } return nextTimes } + +// LastN returns a slice of `n` closest time instants immediately before +// `fromTime` which match the cron expression `expr`. +// +// The time instants in the returned slice are in chronological ascending order. +// The `time.Location` of the returned time instants is the same as that of +// `fromTime`. +// +// A slice with len between [0-`n`] is returned, that is, if not enough existing +// matching time instants exist, the number of returned entries will be less +// than `n`. +func (expr *Expression) LastN(fromTime time.Time, n uint) []time.Time { + nextTimes := make([]time.Time, 0, n) + if n > 0 { + fromTime = expr.Last(fromTime) + for { + if fromTime.IsZero() { + break + } + nextTimes = append(nextTimes, fromTime) + n-- + if n == 0 { + break + } + fromTime = expr.lastSecond(fromTime) + } + } + return nextTimes +} diff --git a/cronexpr_next.go b/cronexpr_next.go index a0ebdb6..e8e2b3c 100644 --- a/cronexpr_next.go +++ b/cronexpr_next.go @@ -66,6 +66,50 @@ func (expr *Expression) nextYear(t time.Time) time.Time { /******************************************************************************/ +func (expr *Expression) lastYear(t time.Time, acc bool) time.Time { + // candidate year + v := t.Year() + if acc { + v-- + } + i := sort.SearchInts(expr.yearList, v) + var year int + if i < len(expr.yearList) && v == expr.yearList[i] { + year = expr.yearList[i] + } else if i == 0 { + return time.Time{} + } else { + year = expr.yearList[i-1] + } + // Year changed, need to recalculate actual days of month + expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth( + year, + expr.monthList[len(expr.monthList)-1]) + + if len(expr.actualDaysOfMonthList) == 0 { + return expr.lastMonth(time.Date( + year, + time.Month(expr.monthList[len(expr.monthList)-1]), + 1, + expr.hourList[len(expr.hourList)-1], + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()), true) + } + return time.Date( + year, + time.Month(expr.monthList[len(expr.monthList)-1]), + expr.actualDaysOfMonthList[len(expr.actualDaysOfMonthList)-1], + expr.hourList[len(expr.hourList)-1], + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()) +} + +/******************************************************************************/ + func (expr *Expression) nextMonth(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate month @@ -100,7 +144,51 @@ func (expr *Expression) nextMonth(t time.Time) time.Time { /******************************************************************************/ -func (expr *Expression) nextDayOfMonth(t time.Time) time.Time { +func (expr *Expression) lastMonth(t time.Time, acc bool) time.Time { + // candidate month + v := int(t.Month()) + if acc { + v-- + } + i := sort.SearchInts(expr.monthList, v) + + var month int + if i < len(expr.monthList) && v == expr.monthList[i] { + month = expr.monthList[i] + } else if i == 0 { + return expr.lastYear(t, true) + } else { + month = expr.monthList[i-1] + } + + // Month changed, need to recalculate actual days of month + expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(t.Year(), month) + if len(expr.actualDaysOfMonthList) == 0 { + return expr.lastMonth(time.Date( + t.Year(), + time.Month(month), + 1, + expr.hourList[len(expr.hourList)-1], + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()), true) + } + + return time.Date( + t.Year(), + time.Month(month), + expr.actualDaysOfMonthList[len(expr.actualDaysOfMonthList)-1], + expr.hourList[len(expr.hourList)-1], + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()) +} + +/******************************************************************************/ + +func (expr *Expression) nextActualDayOfMonth(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate day of month i := sort.SearchInts(expr.actualDaysOfMonthList, t.Day()+1) @@ -121,12 +209,42 @@ func (expr *Expression) nextDayOfMonth(t time.Time) time.Time { /******************************************************************************/ +func (expr *Expression) lastActualDayOfMonth(t time.Time, acc bool) time.Time { + // candidate day of month + v := t.Day() + if acc { + v-- + } + i := sort.SearchInts(expr.actualDaysOfMonthList, v) + + var day int + if i < len(expr.actualDaysOfMonthList) && v == expr.actualDaysOfMonthList[i] { + day = expr.actualDaysOfMonthList[i] + } else if i == 0 { + return expr.lastMonth(t, true) + } else { + day = expr.actualDaysOfMonthList[i-1] + } + + return time.Date( + t.Year(), + t.Month(), + day, + expr.hourList[len(expr.hourList)-1], + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()) +} + +/******************************************************************************/ + func (expr *Expression) nextHour(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate hour i := sort.SearchInts(expr.hourList, t.Hour()+1) if i == len(expr.hourList) { - return expr.nextDayOfMonth(t) + return expr.nextActualDayOfMonth(t) } return time.Date( @@ -142,6 +260,36 @@ func (expr *Expression) nextHour(t time.Time) time.Time { /******************************************************************************/ +func (expr *Expression) lastHour(t time.Time, acc bool) time.Time { + // candidate hour + v := t.Hour() + if acc { + v-- + } + i := sort.SearchInts(expr.hourList, v) + + var hour int + if i < len(expr.hourList) && v == expr.hourList[i] { + hour = expr.hourList[i] + } else if i == 0 { + return expr.lastActualDayOfMonth(t, true) + } else { + hour = expr.hourList[i-1] + } + + return time.Date( + t.Year(), + t.Month(), + t.Day(), + hour, + expr.minuteList[len(expr.minuteList)-1], + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()) +} + +/******************************************************************************/ + func (expr *Expression) nextMinute(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate minute @@ -163,12 +311,42 @@ func (expr *Expression) nextMinute(t time.Time) time.Time { /******************************************************************************/ +func (expr *Expression) lastMinute(t time.Time, acc bool) time.Time { + // candidate minute + v := t.Minute() + if !acc { + v-- + } + i := sort.SearchInts(expr.minuteList, v) + var min int + if i < len(expr.minuteList) && v == expr.minuteList[i] { + min = expr.minuteList[i] + } else if i == 0 { + return expr.lastHour(t, true) + } else { + min = expr.minuteList[i-1] + } + + return time.Date( + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + min, + expr.secondList[len(expr.secondList)-1], + 0, + t.Location()) +} + +/******************************************************************************/ + func (expr *Expression) nextSecond(t time.Time) time.Time { // nextSecond() assumes all other fields are exactly matched // to the cron expression // Find index at which item in list is greater or equal to // candidate second + i := sort.SearchInts(expr.secondList, t.Second()+1) if i == len(expr.secondList) { return expr.nextMinute(t) @@ -185,6 +363,27 @@ func (expr *Expression) nextSecond(t time.Time) time.Time { t.Location()) } +// lastSecond() assumes all other fields are exactly matched +// to the cron expression +func (expr *Expression) lastSecond(t time.Time) time.Time { + // candidate second + v := t.Second() - 1 + i := sort.SearchInts(expr.secondList, v) + if i == len(expr.secondList) || expr.secondList[i] != v { + return expr.lastMinute(t, false) + } + + return time.Date( + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + expr.secondList[i], + 0, + t.Location()) +} + /******************************************************************************/ func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int { diff --git a/cronexpr_test.go b/cronexpr_test.go index f170769..ccc236d 100644 --- a/cronexpr_test.go +++ b/cronexpr_test.go @@ -199,6 +199,248 @@ var crontests = []crontest{ // TODO: more tests } +var cronbackwardtests = []crontest{ + // Seconds + { + "* * * * * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2013-01-01 00:00:01", "2013-01-01 00:00:00"}, + {"2013-01-01 00:01:00", "2013-01-01 00:00:59"}, + {"2013-01-01 01:00:00", "2013-01-01 00:59:59"}, + {"2013-01-02 00:00:00", "2013-01-01 23:59:59"}, + {"2013-03-01 00:00:00", "2013-02-28 23:59:59"}, + {"2016-02-29 00:00:00", "2016-02-28 23:59:59"}, + {"2013-01-01 00:00:00", "2012-12-31 23:59:59"}, + }, + }, + + // every 5 Second + { + "*/5 * * * * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2013-01-01 00:00:06", "2013-01-01 00:00:05"}, + {"2013-01-01 00:01:00", "2013-01-01 00:00:55"}, + {"2013-01-01 01:00:00", "2013-01-01 00:59:55"}, + {"2013-01-02 00:00:00", "2013-01-01 23:59:55"}, + {"2013-03-01 00:00:00", "2013-02-28 23:59:55"}, + {"2016-02-29 00:00:00", "2016-02-28 23:59:55"}, + {"2013-01-01 00:00:00", "2012-12-31 23:59:55"}, + }, + }, + + // Minutes + { + "* * * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2013-01-01 00:00:58", "2013-01-01 00:00:00"}, + {"2013-01-01 00:01:00", "2013-01-01 00:00:00"}, + {"2013-01-01 01:00:00", "2013-01-01 00:59:00"}, + {"2013-01-02 00:00:00", "2013-01-01 23:59:00"}, + {"2013-03-01 00:00:00", "2013-02-28 23:59:00"}, + {"2016-02-29 00:00:00", "2016-02-28 23:59:00"}, + {"2013-01-01 00:00:00", "2012-12-31 23:59:00"}, + }, + }, + + // // Minutes with interval + { + "17-43/5 * * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2013-01-01 00:17:01", "2013-01-01 00:17:00"}, + {"2013-01-01 00:33:00", "2013-01-01 00:32:00"}, + {"2013-01-01 01:00:00", "2013-01-01 00:42:00"}, + {"2013-01-02 00:01:00", "2013-01-01 23:42:00"}, + {"2013-03-01 00:01:00", "2013-02-28 23:42:00"}, + {"2016-02-29 00:01:00", "2016-02-28 23:42:00"}, + {"2013-01-01 00:01:00", "2012-12-31 23:42:00"}, + }, + }, + + // Minutes interval, list + { + "15-30/4,55 * * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2013-01-01 00:16:00", "2013-01-01 00:15:00"}, + {"2013-01-01 00:18:59", "2013-01-01 00:15:00"}, + {"2013-01-01 00:19:00", "2013-01-01 00:15:00"}, + {"2013-01-01 00:56:00", "2013-01-01 00:55:00"}, + {"2013-01-01 01:15:00", "2013-01-01 00:55:00"}, + {"2013-01-02 00:15:00", "2013-01-01 23:55:00"}, + {"2013-03-01 00:15:00", "2013-02-28 23:55:00"}, + {"2016-02-29 00:15:00", "2016-02-28 23:55:00"}, + {"2012-12-31 23:54:00", "2012-12-31 23:27:00"}, + {"2013-01-01 00:15:00", "2012-12-31 23:55:00"}, + }, + }, + + // Hour interval + { + "* 9-19/3 * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-01-01 00:10:00", "2016-12-31 18:59:00"}, + {"2017-02-01 00:10:00", "2017-01-31 18:59:00"}, + {"2017-02-12 00:10:00", "2017-02-11 18:59:00"}, + {"2017-02-12 19:10:00", "2017-02-12 18:59:00"}, + {"2017-02-12 12:15:00", "2017-02-12 12:14:00"}, + {"2017-02-12 13:00:00", "2017-02-12 12:59:00"}, + {"2017-02-12 11:00:00", "2017-02-12 09:59:00"}, + }, + }, + + // Hour interval, list + { + "5 12-21/3,23 * * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-01-01 00:10:00", "2016-12-31 23:05:00"}, + {"2017-02-01 00:10:00", "2017-01-31 23:05:00"}, + {"2017-02-12 00:10:00", "2017-02-11 23:05:00"}, + {"2017-02-12 19:10:00", "2017-02-12 18:05:00"}, + {"2017-02-12 12:15:00", "2017-02-12 12:05:00"}, + {"2017-02-12 22:00:00", "2017-02-12 21:05:00"}, + }, + }, + + // Day interval + { + "5 10-17 12-25/4 * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-01-01 00:10:00", "2016-12-24 17:05:00"}, + {"2017-02-01 10:10:00", "2017-01-24 17:05:00"}, + {"2017-02-27 13:10:00", "2017-02-24 17:05:00"}, + {"2017-02-23 13:10:00", "2017-02-20 17:05:00"}, + {"2017-02-11 13:10:00", "2017-01-24 17:05:00"}, + }, + }, + + // Day interval, list + { + "* * 12-15,20-22 * *", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-01-01 00:20:00", "2016-12-22 23:59:00"}, + {"2017-02-01 10:30:00", "2017-01-22 23:59:00"}, + {"2017-02-27 13:40:00", "2017-02-22 23:59:00"}, + {"2017-02-17 16:10:00", "2017-02-15 23:59:00"}, + {"2017-02-11 13:10:00", "2017-01-22 23:59:00"}, + }, + }, + + // Month + { + "5 10 1 4-6 *", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-01-01 00:10:00", "2016-06-01 10:05:00"}, + {"2017-07-01 10:01:00", "2017-06-01 10:05:00"}, + {"2017-06-03 00:10:00", "2017-06-01 10:05:00"}, + }, + }, + + // Month + { + "0 0 0 12 * * 2017-2020", + "2006-01-02 15:04:05", + []crontimes{ + {"2017-12-11 00:10:00", "2017-11-12 00:00:00"}, + {"2023-01-11 00:10:00", "2020-12-12 00:00:00"}, + {"2021-01-11 00:10:00", "2020-12-12 00:00:00"}, + }, + }, + + // Days of week + { + "0 0 * * MON", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2013-01-10 00:00:00", "Mon 2013-01-07 00:00"}, + {"2017-08-07 00:00:00", "Mon 2017-07-31 00:00"}, + {"2017-01-01 00:30:00", "Mon 2016-12-26 00:00"}, + }, + }, + { + "0 0 * * friday", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-08-14 00:00:00", "Fri 2017-08-11 00:00"}, + {"2017-08-02 00:00:00", "Fri 2017-07-28 00:00"}, + {"2018-01-02 00:30:00", "Fri 2017-12-29 00:00"}, + }, + }, + { + "0 0 * * 6,7", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-09-04 00:00:00", "Sun 2017-09-03 00:00"}, + {"2017-08-02 00:00:00", "Sun 2017-07-30 00:00"}, + {"2018-01-03 00:30:00", "Sun 2017-12-31 00:00"}, + }, + }, + + // // Specific days of week + { + "0 0 * * 6#5", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-03-03 00:00:00", "Sat 2016-12-31 00:00"}, + }, + }, + + // // Work day of month + { + "0 0 18W * *", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-12-02 00:00:00", "Fri 2017-11-17 00:00"}, + {"2017-10-12 00:00:00", "Mon 2017-09-18 00:00"}, + {"2017-08-30 00:00:00", "Fri 2017-08-18 00:00"}, + {"2017-06-21 00:00:00", "Mon 2017-06-19 00:00"}, + }, + }, + + // // Work day of month -- end of month + { + "0 0 30W * *", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-03-02 00:00:00", "Mon 2017-01-30 00:00"}, + {"2017-06-02 00:00:00", "Tue 2017-05-30 00:00"}, + {"2017-08-02 00:00:00", "Mon 2017-07-31 00:00"}, + {"2017-11-02 00:00:00", "Mon 2017-10-30 00:00"}, + }, + }, + + // // Last day of month + { + "0 0 L * *", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2017-01-02 00:00:00", "Sat 2016-12-31 00:00"}, + {"2017-02-01 00:00:00", "Tue 2017-01-31 00:00"}, + {"2017-03-01 00:00:00", "Tue 2017-02-28 00:00"}, + {"2016-03-15 00:00:00", "Mon 2016-02-29 00:00"}, + }, + }, + + // // Last work day of month + { + "0 0 LW * *", + "Mon 2006-01-02 15:04", + []crontimes{ + {"2016-03-02 00:00:00", "Mon 2016-02-29 00:00"}, + {"2017-11-02 00:00:00", "Tue 2017-10-31 00:00"}, + {"2017-08-15 00:00:00", "Mon 2017-07-31 00:00"}, + }, + }, +} + func TestExpressions(t *testing.T) { for _, test := range crontests { for _, times := range test.times { @@ -216,6 +458,23 @@ func TestExpressions(t *testing.T) { } } +func TestBackwardExpressions(t *testing.T) { + for _, test := range cronbackwardtests { + for _, times := range test.times { + from, _ := time.Parse("2006-01-02 15:04:05", times.from) + expr, err := cronexpr.Parse(test.expr) + if err != nil { + t.Errorf(`cronexpr.Parse("%s") returned "%s"`, test.expr, err.Error()) + } + last := expr.Last(from) + laststr := last.Format(test.layout) + if laststr != times.next { + t.Errorf(`("%s").Last("%s") = "%s", got "%s"`, test.expr, times.from, times.next, laststr) + } + } + } +} + /******************************************************************************/ func TestZero(t *testing.T) { @@ -261,6 +520,33 @@ func TestNextN(t *testing.T) { } } +func TestLastN(t *testing.T) { + expected := []string{ + "Sat, 29 Nov 2014 00:00:00", + "Sat, 30 Aug 2014 00:00:00", + "Sat, 31 May 2014 00:00:00", + "Sat, 29 Mar 2014 00:00:00", + "Sat, 30 Nov 2013 00:00:00", + } + layout := "2006-01-02 15:04:05" + ts := "2015-01-02 08:44:30" + expr := "0 0 * * 6#5" + from, _ := time.Parse(layout, ts) + result := cronexpr.MustParse(expr). + LastN(from, uint(len(expected))) + if len(result) != len(expected) { + t.Errorf(`MustParse("%s").LastN("%s", 5):\n"`, expr, ts) + t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result)) + } + for i, next := range result { + nextStr := next.Format("Mon, 2 Jan 2006 15:04:15") + if nextStr != expected[i] { + t.Errorf(`MustParse("%s").LastN("%s", 5):\n"`, expr, ts) + t.Errorf(` result[%d]: expected "%s" but got "%s"`, i, expected[i], nextStr) + } + } +} + func TestNextN_every5min(t *testing.T) { expected := []string{ "Mon, 2 Sep 2013 08:45:00", @@ -278,14 +564,41 @@ func TestNextN_every5min(t *testing.T) { for i, next := range result { nextStr := next.Format("Mon, 2 Jan 2006 15:04:05") if nextStr != expected[i] { - t.Errorf(`MustParse("*/5 * * * *").NextN("2013-09-02 08:44:30", 5):\n"`) + t.Errorf(`MustParse("*/5 * * * *").NextN("2013-09-02 08:44:30", 5):\n"`) + t.Errorf(` result[%d]: expected "%s" but got "%s"`, i, expected[i], nextStr) + } + } +} + +func TestLastN_every5min(t *testing.T) { + expected := []string{ + "Mon, 2 Sep 2013 09:05:00", + "Mon, 2 Sep 2013 09:00:00", + "Mon, 2 Sep 2013 08:55:00", + "Mon, 2 Sep 2013 08:50:00", + "Mon, 2 Sep 2013 08:45:00", + } + layout := "2006-01-02 15:04:05" + ts := "2013-09-02 09:08:32" + cron := "*/5 * * * *" + from, _ := time.Parse(layout, ts) + result := cronexpr.MustParse(cron). + LastN(from, uint(len(expected))) + if len(result) != len(expected) { + t.Errorf(`MustParse("%s").LastN("%s", 5):\n"`, cron, ts) + t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result)) + } + for i, next := range result { + nextStr := next.Format("Mon, 2 Jan 2006 15:04:05") + if nextStr != expected[i] { + t.Errorf(`MustParse("%s").LastN("%s", 5):\n"`, cron, ts) t.Errorf(` result[%d]: expected "%s" but got "%s"`, i, expected[i], nextStr) } } } // Issue: https://github.com/gorhill/cronexpr/issues/16 -func TestInterval_Interval60Issue(t *testing.T){ +func TestInterval_Interval60Issue(t *testing.T) { _, err := cronexpr.Parse("*/60 * * * * *") if err == nil { t.Errorf("parsing with interval 60 should return err")