Skip to content

Commit

Permalink
feat: support mixed range type in cron expression (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
reugn authored Oct 7, 2023
1 parent cdb58bc commit 9d582d9
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 16 deletions.
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

0 comments on commit 9d582d9

Please sign in to comment.