diff --git a/quartz/cron.go b/quartz/cron.go index ea28d58..f9e1577 100644 --- a/quartz/cron.go +++ b/quartz/cron.go @@ -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] @@ -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 @@ -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") @@ -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") @@ -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) { diff --git a/quartz/cron_test.go b/quartz/cron_test.go index 106a7dc..52fa2b3 100644 --- a/quartz/cron_test.go +++ b/quartz/cron_test.go @@ -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()) diff --git a/quartz/util.go b/quartz/util.go index dfa07e4..b06b2fb 100644 --- a/quartz/util.go +++ b/quartz/util.go @@ -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 {