2013-09-07 18:02:17 +00:00
|
|
|
/*!
|
|
|
|
* Copyright 2013 Raymond Hill
|
|
|
|
*
|
|
|
|
* Project: github.com/gorhill/cronexpr
|
2013-09-07 21:11:53 +00:00
|
|
|
* File: cronexpr_next.go
|
2013-09-07 18:02:17 +00:00
|
|
|
* Version: 1.0
|
2013-11-20 11:10:35 +00:00
|
|
|
* License: pick the one which suits you :
|
|
|
|
* GPL v3 see <https://www.gnu.org/licenses/gpl.html>
|
|
|
|
* APL v2 see <http://www.apache.org/licenses/LICENSE-2.0>
|
2013-09-07 18:02:17 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cronexpr
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2013-09-11 15:18:45 +00:00
|
|
|
var dowNormalizedOffsets = [][]int{
|
|
|
|
{1, 8, 15, 22, 29},
|
|
|
|
{2, 9, 16, 23, 30},
|
|
|
|
{3, 10, 17, 24, 31},
|
|
|
|
{4, 11, 18, 25},
|
|
|
|
{5, 12, 19, 26},
|
|
|
|
{6, 13, 20, 27},
|
|
|
|
{7, 14, 21, 28},
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2013-09-07 18:02:17 +00:00
|
|
|
func (expr *Expression) nextYear(t time.Time) time.Time {
|
|
|
|
// Find index at which item in list is greater or equal to
|
|
|
|
// candidate year
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.YearList, t.Year()+1)
|
|
|
|
if i == len(expr.YearList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return time.Time{}
|
|
|
|
}
|
|
|
|
// Year changed, need to recalculate actual days of month
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.ActualDaysOfMonthList = expr.calculateActualDaysOfMonth(expr.YearList[i], expr.MonthList[0])
|
|
|
|
if len(expr.ActualDaysOfMonthList) == 0 {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextMonth(time.Date(
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.YearList[i],
|
|
|
|
time.Month(expr.MonthList[0]),
|
2013-09-07 18:02:17 +00:00
|
|
|
1,
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.HourList[0],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
0,
|
|
|
|
t.Location()))
|
|
|
|
}
|
|
|
|
return time.Date(
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.YearList[i],
|
|
|
|
time.Month(expr.MonthList[0]),
|
|
|
|
expr.ActualDaysOfMonthList[0],
|
|
|
|
expr.HourList[0],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
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
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.MonthList, int(t.Month())+1)
|
|
|
|
if i == len(expr.MonthList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextYear(t)
|
|
|
|
}
|
|
|
|
// Month changed, need to recalculate actual days of month
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.ActualDaysOfMonthList = expr.calculateActualDaysOfMonth(t.Year(), expr.MonthList[i])
|
|
|
|
if len(expr.ActualDaysOfMonthList) == 0 {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextMonth(time.Date(
|
|
|
|
t.Year(),
|
2018-08-18 16:44:57 +00:00
|
|
|
time.Month(expr.MonthList[i]),
|
2013-09-07 18:02:17 +00:00
|
|
|
1,
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.HourList[0],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
0,
|
|
|
|
t.Location()))
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Date(
|
|
|
|
t.Year(),
|
2018-08-18 16:44:57 +00:00
|
|
|
time.Month(expr.MonthList[i]),
|
|
|
|
expr.ActualDaysOfMonthList[0],
|
|
|
|
expr.HourList[0],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
0,
|
|
|
|
t.Location())
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
func (expr *Expression) nextDayOfMonth(t time.Time) time.Time {
|
|
|
|
// Find index at which item in list is greater or equal to
|
|
|
|
// candidate day of month
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.ActualDaysOfMonthList, t.Day()+1)
|
|
|
|
if i == len(expr.ActualDaysOfMonthList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextMonth(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Date(
|
|
|
|
t.Year(),
|
|
|
|
t.Month(),
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.ActualDaysOfMonthList[i],
|
|
|
|
expr.HourList[0],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
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
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.HourList, t.Hour()+1)
|
|
|
|
if i == len(expr.HourList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextDayOfMonth(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Date(
|
|
|
|
t.Year(),
|
|
|
|
t.Month(),
|
|
|
|
t.Day(),
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.HourList[i],
|
|
|
|
expr.MinuteList[0],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
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
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.MinuteList, t.Minute()+1)
|
|
|
|
if i == len(expr.MinuteList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextHour(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Date(
|
|
|
|
t.Year(),
|
|
|
|
t.Month(),
|
|
|
|
t.Day(),
|
|
|
|
t.Hour(),
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.MinuteList[i],
|
|
|
|
expr.SecondList[0],
|
2013-09-07 18:02:17 +00:00
|
|
|
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
|
2018-08-18 16:44:57 +00:00
|
|
|
i := sort.SearchInts(expr.SecondList, t.Second()+1)
|
|
|
|
if i == len(expr.SecondList) {
|
2013-09-07 18:02:17 +00:00
|
|
|
return expr.nextMinute(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Date(
|
|
|
|
t.Year(),
|
|
|
|
t.Month(),
|
|
|
|
t.Day(),
|
|
|
|
t.Hour(),
|
|
|
|
t.Minute(),
|
2018-08-18 16:44:57 +00:00
|
|
|
expr.SecondList[i],
|
2013-09-07 18:02:17 +00:00
|
|
|
0,
|
|
|
|
t.Location())
|
|
|
|
}
|
2013-09-08 14:37:12 +00:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int {
|
|
|
|
actualDaysOfMonthMap := make(map[int]bool)
|
|
|
|
firstDayOfMonth := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, -1)
|
|
|
|
|
|
|
|
// As per crontab man page (http://linux.die.net/man/5/crontab#):
|
|
|
|
// "The day of a command's execution can be specified by two
|
|
|
|
// "fields - day of month, and day of week. If both fields are
|
|
|
|
// "restricted (ie, aren't *), the command will be run when
|
|
|
|
// "either field matches the current time"
|
2013-09-10 20:09:51 +00:00
|
|
|
|
|
|
|
// If both fields are not restricted, all days of the month are a hit
|
2018-08-18 16:44:57 +00:00
|
|
|
if expr.DaysOfMonthRestricted == false && expr.DaysOfWeekRestricted == false {
|
2013-09-10 20:09:51 +00:00
|
|
|
return genericDefaultList[1 : lastDayOfMonth.Day()+1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// day-of-month != `*`
|
2018-08-18 16:44:57 +00:00
|
|
|
if expr.DaysOfMonthRestricted {
|
2013-09-08 14:37:12 +00:00
|
|
|
// Last day of month
|
2018-08-18 16:44:57 +00:00
|
|
|
if expr.LastDayOfMonth {
|
2013-09-08 14:37:12 +00:00
|
|
|
actualDaysOfMonthMap[lastDayOfMonth.Day()] = true
|
|
|
|
}
|
|
|
|
// Last work day of month
|
2018-08-18 16:44:57 +00:00
|
|
|
if expr.LastWorkdayOfMonth {
|
2013-09-08 14:37:12 +00:00
|
|
|
actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true
|
|
|
|
}
|
|
|
|
// Days of month
|
2018-08-18 16:44:57 +00:00
|
|
|
for v := range expr.DaysOfMonth {
|
2013-09-08 14:37:12 +00:00
|
|
|
// Ignore days beyond end of month
|
|
|
|
if v <= lastDayOfMonth.Day() {
|
|
|
|
actualDaysOfMonthMap[v] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Work days of month
|
|
|
|
// As per Wikipedia: month boundaries are not crossed.
|
2018-08-18 16:44:57 +00:00
|
|
|
for v := range expr.WorkdaysOfMonth {
|
2013-09-08 14:37:12 +00:00
|
|
|
// Ignore days beyond end of month
|
|
|
|
if v <= lastDayOfMonth.Day() {
|
|
|
|
actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 20:09:51 +00:00
|
|
|
// day-of-week != `*`
|
2018-08-18 16:44:57 +00:00
|
|
|
if expr.DaysOfWeekRestricted {
|
2013-09-08 14:37:12 +00:00
|
|
|
// How far first sunday is from first day of month
|
|
|
|
offset := 7 - int(firstDayOfMonth.Weekday())
|
|
|
|
// days of week
|
|
|
|
// offset : (7 - day_of_week_of_1st_day_of_month)
|
|
|
|
// target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7
|
2018-08-18 16:44:57 +00:00
|
|
|
for v := range expr.DaysOfWeek {
|
2013-09-11 15:24:07 +00:00
|
|
|
w := dowNormalizedOffsets[(offset+v)%7]
|
2013-09-11 15:18:45 +00:00
|
|
|
actualDaysOfMonthMap[w[0]] = true
|
|
|
|
actualDaysOfMonthMap[w[1]] = true
|
|
|
|
actualDaysOfMonthMap[w[2]] = true
|
|
|
|
actualDaysOfMonthMap[w[3]] = true
|
|
|
|
if len(w) > 4 && w[4] <= lastDayOfMonth.Day() {
|
|
|
|
actualDaysOfMonthMap[w[4]] = true
|
2013-09-08 14:37:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// days of week of specific week in the month
|
|
|
|
// offset : (7 - day_of_week_of_1st_day_of_month)
|
|
|
|
// target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7
|
2018-08-18 16:44:57 +00:00
|
|
|
for v := range expr.SpecificWeekDaysOfWeek {
|
2013-09-11 15:18:45 +00:00
|
|
|
v = 1 + 7*(v/7) + (offset+v)%7
|
2013-09-08 14:37:12 +00:00
|
|
|
if v <= lastDayOfMonth.Day() {
|
|
|
|
actualDaysOfMonthMap[v] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Last days of week of the month
|
|
|
|
lastWeekOrigin := firstDayOfMonth.AddDate(0, 1, -7)
|
|
|
|
offset = 7 - int(lastWeekOrigin.Weekday())
|
2018-08-18 16:44:57 +00:00
|
|
|
for v := range expr.LastWeekDaysOfWeek {
|
2013-09-11 15:18:45 +00:00
|
|
|
v = lastWeekOrigin.Day() + (offset+v)%7
|
2013-09-08 14:37:12 +00:00
|
|
|
if v <= lastDayOfMonth.Day() {
|
|
|
|
actualDaysOfMonthMap[v] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return toList(actualDaysOfMonthMap)
|
|
|
|
}
|
|
|
|
|
|
|
|
func workdayOfMonth(targetDom, lastDom time.Time) int {
|
2013-09-11 15:18:45 +00:00
|
|
|
// If saturday, then friday
|
|
|
|
// If sunday, then monday
|
2013-09-08 14:37:12 +00:00
|
|
|
dom := targetDom.Day()
|
|
|
|
dow := targetDom.Weekday()
|
|
|
|
if dow == time.Saturday {
|
|
|
|
if dom > 1 {
|
|
|
|
dom -= 1
|
|
|
|
} else {
|
|
|
|
dom += 2
|
|
|
|
}
|
|
|
|
} else if dow == time.Sunday {
|
|
|
|
if dom < lastDom.Day() {
|
|
|
|
dom += 1
|
|
|
|
} else {
|
|
|
|
dom -= 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dom
|
|
|
|
}
|