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.
|
||||
|
||||
#### 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 ( # )
|
||||
`#` 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)
|
||||
|
||||
indices := fieldFinder.FindAllStringIndex(cron, -1)
|
||||
if len(indices) < 5 {
|
||||
fieldCount := len(indices)
|
||||
if fieldCount < 5 {
|
||||
return nil, fmt.Errorf("missing field(s)")
|
||||
}
|
||||
// ignore fields beyond 7th
|
||||
if fieldCount > 7 {
|
||||
fieldCount = 7
|
||||
}
|
||||
|
||||
expr := Expression{}
|
||||
field := 0
|
||||
var expr = Expression{}
|
||||
var field = 0
|
||||
var err error
|
||||
|
||||
// second field (optional)
|
||||
if len(indices) >= 7 {
|
||||
err := expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||
if fieldCount == 7 {
|
||||
err = expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,7 +95,7 @@ func Parse(cronLine string) (*Expression, error) {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -124,7 +130,7 @@ func Parse(cronLine string) (*Expression, error) {
|
|||
field += 1
|
||||
|
||||
// year field
|
||||
if field < len(indices) {
|
||||
if field < fieldCount {
|
||||
err = expr.yearFieldHandler(cron[indices[field][0]:indices[field][1]])
|
||||
if err != nil {
|
||||
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 {
|
||||
// Find index at which item in list is greater or equal to
|
||||
// candidate day of month
|
||||
|
@ -266,3 +170,99 @@ func (expr *Expression) nextSecond(t time.Time) time.Time {
|
|||
0,
|
||||
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) {
|
||||
// At least one entry must be present
|
||||
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 {
|
||||
layout = strings.Replace(layout, `%value%`, value, -1)
|
||||
re := layoutRegexp[layout]
|
||||
|
|
Loading…
Reference in New Issue