Add previous commands

This commit is contained in:
Cheung Chifung 2017-08-07 14:58:57 +09:00
parent d520615e53
commit 7d34e02fab
3 changed files with 629 additions and 7 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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")