Add previous commands
This commit is contained in:
parent
d520615e53
commit
7d34e02fab
116
cronexpr.go
116
cronexpr.go
|
@ -201,13 +201,13 @@ func (expr *Expression) Next(fromTime time.Time) time.Time {
|
||||||
return expr.nextMonth(fromTime)
|
return expr.nextMonth(fromTime)
|
||||||
}
|
}
|
||||||
if v != expr.actualDaysOfMonthList[i] {
|
if v != expr.actualDaysOfMonthList[i] {
|
||||||
return expr.nextDayOfMonth(fromTime)
|
return expr.nextActualDayOfMonth(fromTime)
|
||||||
}
|
}
|
||||||
// hour
|
// hour
|
||||||
v = fromTime.Hour()
|
v = fromTime.Hour()
|
||||||
i = sort.SearchInts(expr.hourList, v)
|
i = sort.SearchInts(expr.hourList, v)
|
||||||
if i == len(expr.hourList) {
|
if i == len(expr.hourList) {
|
||||||
return expr.nextDayOfMonth(fromTime)
|
return expr.nextActualDayOfMonth(fromTime)
|
||||||
}
|
}
|
||||||
if v != expr.hourList[i] {
|
if v != expr.hourList[i] {
|
||||||
return expr.nextHour(fromTime)
|
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
|
// NextN returns a slice of `n` closest time instants immediately following
|
||||||
// `fromTime` which match the cron expression `expr`.
|
// `fromTime` which match the cron expression `expr`.
|
||||||
//
|
//
|
||||||
|
@ -255,7 +336,7 @@ func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
nextTimes = append(nextTimes, fromTime)
|
nextTimes = append(nextTimes, fromTime)
|
||||||
n -= 1
|
n--
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -264,3 +345,32 @@ func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time {
|
||||||
}
|
}
|
||||||
return nextTimes
|
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
|
||||||
|
}
|
||||||
|
|
203
cronexpr_next.go
203
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 {
|
func (expr *Expression) nextMonth(t time.Time) time.Time {
|
||||||
// Find index at which item in list is greater or equal to
|
// Find index at which item in list is greater or equal to
|
||||||
// candidate month
|
// 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
|
// Find index at which item in list is greater or equal to
|
||||||
// candidate day of month
|
// candidate day of month
|
||||||
i := sort.SearchInts(expr.actualDaysOfMonthList, t.Day()+1)
|
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 {
|
func (expr *Expression) nextHour(t time.Time) time.Time {
|
||||||
// Find index at which item in list is greater or equal to
|
// Find index at which item in list is greater or equal to
|
||||||
// candidate hour
|
// candidate hour
|
||||||
i := sort.SearchInts(expr.hourList, t.Hour()+1)
|
i := sort.SearchInts(expr.hourList, t.Hour()+1)
|
||||||
if i == len(expr.hourList) {
|
if i == len(expr.hourList) {
|
||||||
return expr.nextDayOfMonth(t)
|
return expr.nextActualDayOfMonth(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
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 {
|
func (expr *Expression) nextMinute(t time.Time) time.Time {
|
||||||
// Find index at which item in list is greater or equal to
|
// Find index at which item in list is greater or equal to
|
||||||
// candidate minute
|
// 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 {
|
func (expr *Expression) nextSecond(t time.Time) time.Time {
|
||||||
// nextSecond() assumes all other fields are exactly matched
|
// nextSecond() assumes all other fields are exactly matched
|
||||||
// to the cron expression
|
// to the cron expression
|
||||||
|
|
||||||
// Find index at which item in list is greater or equal to
|
// Find index at which item in list is greater or equal to
|
||||||
// candidate second
|
// candidate second
|
||||||
|
|
||||||
i := sort.SearchInts(expr.secondList, t.Second()+1)
|
i := sort.SearchInts(expr.secondList, t.Second()+1)
|
||||||
if i == len(expr.secondList) {
|
if i == len(expr.secondList) {
|
||||||
return expr.nextMinute(t)
|
return expr.nextMinute(t)
|
||||||
|
@ -185,6 +363,27 @@ func (expr *Expression) nextSecond(t time.Time) time.Time {
|
||||||
t.Location())
|
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 {
|
func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int {
|
||||||
|
|
315
cronexpr_test.go
315
cronexpr_test.go
|
@ -199,6 +199,248 @@ var crontests = []crontest{
|
||||||
// TODO: more tests
|
// 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) {
|
func TestExpressions(t *testing.T) {
|
||||||
for _, test := range crontests {
|
for _, test := range crontests {
|
||||||
for _, times := range test.times {
|
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) {
|
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) {
|
func TestNextN_every5min(t *testing.T) {
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"Mon, 2 Sep 2013 08:45:00",
|
"Mon, 2 Sep 2013 08:45:00",
|
||||||
|
@ -284,8 +570,35 @@ func TestNextN_every5min(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Issue: https://github.com/gorhill/cronexpr/issues/16
|
||||||
func TestInterval_Interval60Issue(t *testing.T){
|
func TestInterval_Interval60Issue(t *testing.T) {
|
||||||
_, err := cronexpr.Parse("*/60 * * * * *")
|
_, err := cronexpr.Parse("*/60 * * * * *")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("parsing with interval 60 should return err")
|
t.Errorf("parsing with interval 60 should return err")
|
||||||
|
|
Loading…
Reference in New Issue