further tidying
This commit is contained in:
parent
13ba1270e1
commit
17ea23bf08
|
@ -39,7 +39,13 @@ Hyphens define ranges. For example, 2000-2010 indicates every year between 2000
|
||||||
`L` stands for "last". When used in the day-of-week field, it allows you to specify constructs such as "the last Friday" (`5L`) of a given month. In the day-of-month field, it specifies the last day of the month.
|
`L` stands for "last". When used in the day-of-week field, it allows you to specify constructs such as "the last Friday" (`5L`) of a given month. In the day-of-month field, it specifies the last day of the month.
|
||||||
|
|
||||||
#### W
|
#### W
|
||||||
The `W` character is allowed for the day-of-month field. This character is used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify `15W` as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month." So, if the 15th is a Saturday, the trigger fires on Friday the 14th. If the 15th is a Sunday, the trigger fires on Monday the 16th. If the 15th is a Tuesday, then it fires on Tuesday the 15th. However if you specify `1W` as the value for day-of-month, and the 1st is a Saturday, the trigger fires on Monday the 3rd, as it does not 'jump' over the boundary of a month's days. The `W` character can be specified only when the day-of-month is a single day, not a range or list of days.
|
The `W` character is allowed for the day-of-month field. This character is used to specify the business day (Monday-Friday) nearest the given day. As an example, if you were to specify `15W` as the value for the day-of-month field, the meaning is: "the nearest business day to the 15th of the month."
|
||||||
|
|
||||||
|
So, if the 15th is a Saturday, the trigger fires on Friday the 14th. If the 15th is a Sunday, the trigger fires on Monday the 16th. If the 15th is a Tuesday, then it fires on Tuesday the 15th. However if you specify `1W` as the value for day-of-month, and the 1st is a Saturday, the trigger fires on Monday the 3rd, as it does not 'jump' over the boundary of a month's days.
|
||||||
|
|
||||||
|
The `W` character can be specified only when the day-of-month is a single day, not a range or list of days.
|
||||||
|
|
||||||
|
The `W` character can also be combined with `L` to mean "the last business day of the month."
|
||||||
|
|
||||||
#### Hash ( # )
|
#### Hash ( # )
|
||||||
`#` is allowed for the day-of-week field, and must be followed by a number between one and five. It allows you to specify constructs such as "the second Friday" of a given month.
|
`#` is allowed for the day-of-week field, and must be followed by a number between one and five. It allows you to specify constructs such as "the second Friday" of a given month.
|
||||||
|
|
20
cronexpr.go
20
cronexpr.go
|
@ -70,16 +70,22 @@ func Parse(cronLine string) (*Expression, error) {
|
||||||
cron := cronNormalizer.Replace(cronLine)
|
cron := cronNormalizer.Replace(cronLine)
|
||||||
|
|
||||||
indices := fieldFinder.FindAllStringIndex(cron, -1)
|
indices := fieldFinder.FindAllStringIndex(cron, -1)
|
||||||
if len(indices) < 5 {
|
fieldCount := len(indices)
|
||||||
|
if fieldCount < 5 {
|
||||||
return nil, fmt.Errorf("missing field(s)")
|
return nil, fmt.Errorf("missing field(s)")
|
||||||
}
|
}
|
||||||
|
// ignore fields beyond 7th
|
||||||
|
if fieldCount > 7 {
|
||||||
|
fieldCount = 7
|
||||||
|
}
|
||||||
|
|
||||||
expr := Expression{}
|
var expr = Expression{}
|
||||||
field := 0
|
var field = 0
|
||||||
|
var err error
|
||||||
|
|
||||||
// second field (optional)
|
// second field (optional)
|
||||||
if len(indices) >= 7 {
|
if fieldCount == 7 {
|
||||||
err := expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
|
err = expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -89,7 +95,7 @@ func Parse(cronLine string) (*Expression, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// minute field
|
// minute field
|
||||||
err := expr.minuteFieldHandler(cron[indices[field][0]:indices[field][1]])
|
err = expr.minuteFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -124,7 +130,7 @@ func Parse(cronLine string) (*Expression, error) {
|
||||||
field += 1
|
field += 1
|
||||||
|
|
||||||
// year field
|
// year field
|
||||||
if field < len(indices) {
|
if field < fieldCount {
|
||||||
err = expr.yearFieldHandler(cron[indices[field][0]:indices[field][1]])
|
err = expr.yearFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
192
cronexpr_next.go
192
cronexpr_next.go
|
@ -86,102 +86,6 @@ func (expr *Expression) nextMonth(t time.Time) time.Time {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
func workdayOfMonth(targetDom, lastDom time.Time) int {
|
|
||||||
dom := targetDom.Day()
|
|
||||||
dow := targetDom.Weekday()
|
|
||||||
// If saturday, then friday
|
|
||||||
if dow == time.Saturday {
|
|
||||||
if dom > 1 {
|
|
||||||
dom -= 1
|
|
||||||
} else {
|
|
||||||
dom += 2
|
|
||||||
}
|
|
||||||
// If sunday, then monday
|
|
||||||
} else if dow == time.Sunday {
|
|
||||||
if dom < lastDom.Day() {
|
|
||||||
dom += 1
|
|
||||||
} else {
|
|
||||||
dom -= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dom
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
if expr.daysOfMonthRestricted || expr.daysOfWeekRestricted == false {
|
|
||||||
// Last day of month
|
|
||||||
if expr.lastDayOfMonth {
|
|
||||||
actualDaysOfMonthMap[lastDayOfMonth.Day()] = true
|
|
||||||
}
|
|
||||||
// Last work day of month
|
|
||||||
if expr.lastWorkdayOfMonth {
|
|
||||||
actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true
|
|
||||||
}
|
|
||||||
// Days of month
|
|
||||||
for v := range expr.daysOfMonth {
|
|
||||||
// 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.
|
|
||||||
for v := range expr.workdaysOfMonth {
|
|
||||||
// Ignore days beyond end of month
|
|
||||||
if v <= lastDayOfMonth.Day() {
|
|
||||||
actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if expr.daysOfWeekRestricted {
|
|
||||||
// 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
|
|
||||||
for w := 0; w <= 4; w += 1 {
|
|
||||||
for v := range expr.daysOfWeek {
|
|
||||||
v := 1 + w*7 + (offset+v)%7
|
|
||||||
if v <= lastDayOfMonth.Day() {
|
|
||||||
actualDaysOfMonthMap[v] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
for v := range expr.specificWeekDaysOfWeek {
|
|
||||||
v := 1 + 7*(v/7) + (offset+v)%7
|
|
||||||
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())
|
|
||||||
for v := range expr.lastWeekDaysOfWeek {
|
|
||||||
v := lastWeekOrigin.Day() + (offset+v)%7
|
|
||||||
if v <= lastDayOfMonth.Day() {
|
|
||||||
actualDaysOfMonthMap[v] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toList(actualDaysOfMonthMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
func (expr *Expression) nextDayOfMonth(t time.Time) time.Time {
|
func (expr *Expression) nextDayOfMonth(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
|
||||||
|
@ -266,3 +170,99 @@ func (expr *Expression) nextSecond(t time.Time) time.Time {
|
||||||
0,
|
0,
|
||||||
t.Location())
|
t.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
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"
|
||||||
|
if expr.daysOfMonthRestricted || expr.daysOfWeekRestricted == false {
|
||||||
|
// Last day of month
|
||||||
|
if expr.lastDayOfMonth {
|
||||||
|
actualDaysOfMonthMap[lastDayOfMonth.Day()] = true
|
||||||
|
}
|
||||||
|
// Last work day of month
|
||||||
|
if expr.lastWorkdayOfMonth {
|
||||||
|
actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true
|
||||||
|
}
|
||||||
|
// Days of month
|
||||||
|
for v := range expr.daysOfMonth {
|
||||||
|
// 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.
|
||||||
|
for v := range expr.workdaysOfMonth {
|
||||||
|
// Ignore days beyond end of month
|
||||||
|
if v <= lastDayOfMonth.Day() {
|
||||||
|
actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.daysOfWeekRestricted {
|
||||||
|
// 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
|
||||||
|
for w := 0; w <= 4; w += 1 {
|
||||||
|
for v := range expr.daysOfWeek {
|
||||||
|
v := 1 + w*7 + (offset+v)%7
|
||||||
|
if v <= lastDayOfMonth.Day() {
|
||||||
|
actualDaysOfMonthMap[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
for v := range expr.specificWeekDaysOfWeek {
|
||||||
|
v := 1 + 7*(v/7) + (offset+v)%7
|
||||||
|
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())
|
||||||
|
for v := range expr.lastWeekDaysOfWeek {
|
||||||
|
v := lastWeekOrigin.Day() + (offset+v)%7
|
||||||
|
if v <= lastDayOfMonth.Day() {
|
||||||
|
actualDaysOfMonthMap[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toList(actualDaysOfMonthMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func workdayOfMonth(targetDom, lastDom time.Time) int {
|
||||||
|
dom := targetDom.Day()
|
||||||
|
dow := targetDom.Weekday()
|
||||||
|
// If saturday, then friday
|
||||||
|
if dow == time.Saturday {
|
||||||
|
if dom > 1 {
|
||||||
|
dom -= 1
|
||||||
|
} else {
|
||||||
|
dom += 2
|
||||||
|
}
|
||||||
|
// If sunday, then monday
|
||||||
|
} else if dow == time.Sunday {
|
||||||
|
if dom < lastDom.Day() {
|
||||||
|
dom += 1
|
||||||
|
} else {
|
||||||
|
dom -= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
|
|
@ -371,6 +371,29 @@ func (expr *Expression) domFieldHandler(s string) error {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
func populateOne(values map[int]bool, v int) {
|
||||||
|
values[v] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateMany(values map[int]bool, min, max, step int) {
|
||||||
|
for i := min; i <= max; i += step {
|
||||||
|
values[i] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toList(set map[int]bool) []int {
|
||||||
|
list := make([]int, len(set))
|
||||||
|
i := 0
|
||||||
|
for k := range set {
|
||||||
|
list[i] = k
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
sort.Ints(list)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) {
|
func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error) {
|
||||||
// At least one entry must be present
|
// At least one entry must be present
|
||||||
indices := entryFinder.FindAllStringIndex(s, -1)
|
indices := entryFinder.FindAllStringIndex(s, -1)
|
||||||
|
@ -451,29 +474,6 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error)
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
func populateOne(values map[int]bool, v int) {
|
|
||||||
values[v] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateMany(values map[int]bool, min, max, step int) {
|
|
||||||
for i := min; i <= max; i += step {
|
|
||||||
values[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toList(set map[int]bool) []int {
|
|
||||||
list := make([]int, len(set))
|
|
||||||
i := 0
|
|
||||||
for k := range set {
|
|
||||||
list[i] = k
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
sort.Ints(list)
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
func makeLayoutRegexp(layout, value string) *regexp.Regexp {
|
func makeLayoutRegexp(layout, value string) *regexp.Regexp {
|
||||||
layout = strings.Replace(layout, `%value%`, value, -1)
|
layout = strings.Replace(layout, `%value%`, value, -1)
|
||||||
re := layoutRegexp[layout]
|
re := layoutRegexp[layout]
|
||||||
|
|
Loading…
Reference in New Issue