Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support mixed range type in cron expression #72

Merged
merged 1 commit into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions quartz/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func buildCronField(tokens []string) ([]*cronField, error) {
return fields, nil
}

func parseField(field string, min int, max int, translate ...[]string) (*cronField, error) {
func parseField(field string, min, max int, translate ...[]string) (*cronField, error) {
var dict []string
if len(translate) > 0 {
dict = translate[0]
Expand All @@ -230,7 +230,7 @@ func parseField(field string, min int, max int, translate ...[]string) (*cronFie

// list values
if strings.Contains(field, ",") {
return parseListField(field, dict)
return parseListField(field, min, max, dict)
}

// range values
Expand All @@ -257,22 +257,29 @@ func parseField(field string, min int, max int, translate ...[]string) (*cronFie
return nil, cronError("cron parse error")
}

func parseListField(field string, translate []string) (*cronField, error) {
func parseListField(field string, min, max int, translate []string) (*cronField, error) {
t := strings.Split(field, ",")
si, err := sliceAtoi(t)
values, rangeValues := extractRangeValues(t)
listValues, err := sliceAtoi(values)
if err != nil {
si, err = indexes(t, translate)
listValues, err = indexes(values, translate)
if err != nil {
return nil, err
}
}
for _, v := range rangeValues {
rangeField, err := parseRangeField(v, min, max, translate)
if err != nil {
return nil, err
}
listValues = append(listValues, rangeField.values...)
}

sort.Ints(si)
return &cronField{si}, nil
sort.Ints(listValues)
return &cronField{listValues}, nil
}

func parseRangeField(field string, min int, max int, translate []string) (*cronField, error) {
var _range []int
func parseRangeField(field string, min, max int, translate []string) (*cronField, error) {
t := strings.Split(field, "-")
if len(t) != 2 {
return nil, cronError("parse cron range error")
Expand All @@ -281,19 +288,19 @@ func parseRangeField(field string, min int, max int, translate []string) (*cronF
from := normalize(t[0], translate)
to := normalize(t[1], translate)
if !inScope(from, min, max) || !inScope(to, min, max) {
return nil, cronError("cron range min/max validation error")
return nil, cronError(fmt.Sprintf("cron range min/max validation error %d-%d",
from, to))
}

_range, err := fillRange(from, to)
rangeValues, err := fillRange(from, to)
if err != nil {
return nil, err
}

return &cronField{_range}, nil
return &cronField{rangeValues}, nil
}

func parseStepField(field string, min int, max int, translate []string) (*cronField, error) {
var _step []int
func parseStepField(field string, min, max int, translate []string) (*cronField, error) {
t := strings.Split(field, "/")
if len(t) != 2 {
return nil, cronError("parse cron step error")
Expand All @@ -309,12 +316,12 @@ func parseStepField(field string, min int, max int, translate []string) (*cronFi
return nil, cronError("cron step min/max validation error")
}

_step, err := fillStep(from, step, max)
stepValues, err := fillStep(from, step, max)
if err != nil {
return nil, err
}

return &cronField{_step}, nil
return &cronField{stepValues}, nil
}

func (parser *cronExpressionParser) nextTime(prev time.Time, fields []*cronField) (int64, error) {
Expand Down
26 changes: 26 additions & 0 deletions quartz/cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,32 @@ func TestCronExpression14(t *testing.T) {
assertEqual(t, result, "Wed May 1 00:00:00 2041")
}

func TestCronExpressionMixedRange(t *testing.T) {
prev := time.Date(2023, 4, 22, 12, 00, 00, 00, time.UTC).UnixNano()
fmt.Println(time.Unix(0, prev).UTC())
result := ""
cronTrigger, err := quartz.NewCronTrigger("0 0 0-2,5,7-9,21-22 * * *")
if err != nil {
t.Fatal(err)
} else {
result, _ = iterate(prev, cronTrigger, 10)
}
assertEqual(t, result, "Sun Apr 23 21:00:00 2023")
}

func TestCronExpressionMixedStringRange(t *testing.T) {
prev := time.Date(2023, 4, 22, 12, 00, 00, 00, time.UTC).UnixNano()
fmt.Println(time.Unix(0, prev).UTC())
result := ""
cronTrigger, err := quartz.NewCronTrigger("0 0 0 ? * SUN,TUE-WED,Fri-Sat")
if err != nil {
t.Fatal(err)
} else {
result, _ = iterate(prev, cronTrigger, 10)
}
assertEqual(t, result, "Sat May 6 00:00:00 2023")
}

func TestCronExpressionExpired(t *testing.T) {
prev := time.Date(2023, 4, 22, 12, 00, 00, 00, time.UTC).UnixNano()
fmt.Println(time.Unix(0, prev).UTC())
Expand Down
13 changes: 13 additions & 0 deletions quartz/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ func indexes(search []string, target []string) ([]int, error) {
return searchIndexes, nil
}

func extractRangeValues(parsed []string) ([]string, []string) {
values := make([]string, 0, len(parsed))
rangeValues := make([]string, 0)
for _, v := range parsed {
if strings.Contains(v, "-") { // range value
rangeValues = append(rangeValues, v)
} else {
values = append(values, v)
}
}
return values, rangeValues
}

func sliceAtoi(sa []string) ([]int, error) {
si := make([]int, 0, len(sa))
for _, a := range sa {
Expand Down