further tidying

This commit is contained in:
gorhill 2013-09-08 10:37:12 -04:00
parent 13ba1270e1
commit 17ea23bf08
4 changed files with 139 additions and 127 deletions

View File

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

View File

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

View File

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

View File

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