Compare commits

...

7 Commits

Author SHA1 Message Date
Raymond Hill 88b0669f7d
Merge pull request #30 from dadgar/b-concurrent
Allow library to be used in parallel
2018-04-27 06:00:37 -04:00
Alex Dadgar 675cac9b2d Allow library to be used in parallel
This PR allows concurrent calls to Parse. Further it makes the test be
part of the same package.

Fixes https://github.com/gorhill/cronexpr/issues/26
2017-09-15 11:30:32 -07:00
Raymond Hill d520615e53 Merge pull request #22 from aiquestion/intervalissue
fix infinite loop on err interval
2016-12-05 09:13:22 -05:00
xiaofan 86090e60c2 fix 60 interval issue 2016-12-05 11:16:12 +08:00
Raymond Hill f0984319b4 Merge pull request #11 from takumakanari/support-numeric-format-starts-with-0
support 0N format for numeric field
2016-03-18 08:17:24 -04:00
takumakanari cfcb638156 modify regex 2016-03-18 16:23:09 +09:00
takumakanari 334aab8a03 support 0N format for numeric field 2016-03-17 17:57:02 +09:00
3 changed files with 58 additions and 24 deletions

View File

@ -19,6 +19,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"sync"
) )
/******************************************************************************/ /******************************************************************************/
@ -54,6 +55,7 @@ var (
var ( var (
numberTokens = map[string]int{ numberTokens = map[string]int{
"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
"00": 0, "01": 1, "02": 2, "03": 3, "04": 4, "05": 5, "06": 6, "07": 7, "08": 8, "09": 9,
"10": 10, "11": 11, "12": 12, "13": 13, "14": 14, "15": 15, "16": 16, "17": 17, "18": 18, "19": 19, "10": 10, "11": 11, "12": 12, "13": 13, "14": 14, "15": 15, "16": 16, "17": 17, "18": 18, "19": 19,
"20": 20, "21": 21, "22": 22, "23": 23, "24": 24, "25": 25, "26": 26, "27": 27, "28": 28, "29": 29, "20": 20, "21": 21, "22": 22, "23": 23, "24": 24, "25": 25, "26": 26, "27": 27, "28": 28, "29": 29,
"30": 30, "31": 31, "32": 32, "33": 33, "34": 34, "35": 35, "36": 36, "37": 37, "38": 38, "39": 39, "30": 30, "31": 31, "32": 32, "33": 33, "34": 34, "35": 35, "36": 36, "37": 37, "38": 38, "39": 39,
@ -119,7 +121,7 @@ var (
min: 0, min: 0,
max: 59, max: 59,
defaultList: genericDefaultList[0:60], defaultList: genericDefaultList[0:60],
valuePattern: `[0-9]|[1-5][0-9]`, valuePattern: `0?[0-9]|[1-5][0-9]`,
atoi: atoi, atoi: atoi,
} }
minuteDescriptor = fieldDescriptor{ minuteDescriptor = fieldDescriptor{
@ -127,7 +129,7 @@ var (
min: 0, min: 0,
max: 59, max: 59,
defaultList: genericDefaultList[0:60], defaultList: genericDefaultList[0:60],
valuePattern: `[0-9]|[1-5][0-9]`, valuePattern: `0?[0-9]|[1-5][0-9]`,
atoi: atoi, atoi: atoi,
} }
hourDescriptor = fieldDescriptor{ hourDescriptor = fieldDescriptor{
@ -135,7 +137,7 @@ var (
min: 0, min: 0,
max: 23, max: 23,
defaultList: genericDefaultList[0:24], defaultList: genericDefaultList[0:24],
valuePattern: `[0-9]|1[0-9]|2[0-3]`, valuePattern: `0?[0-9]|1[0-9]|2[0-3]`,
atoi: atoi, atoi: atoi,
} }
domDescriptor = fieldDescriptor{ domDescriptor = fieldDescriptor{
@ -143,7 +145,7 @@ var (
min: 1, min: 1,
max: 31, max: 31,
defaultList: genericDefaultList[1:32], defaultList: genericDefaultList[1:32],
valuePattern: `[1-9]|[12][0-9]|3[01]`, valuePattern: `0?[1-9]|[12][0-9]|3[01]`,
atoi: atoi, atoi: atoi,
} }
monthDescriptor = fieldDescriptor{ monthDescriptor = fieldDescriptor{
@ -151,7 +153,7 @@ var (
min: 1, min: 1,
max: 12, max: 12,
defaultList: genericDefaultList[1:13], defaultList: genericDefaultList[1:13],
valuePattern: `[1-9]|1[012]|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|march|april|june|july|august|september|october|november|december`, valuePattern: `0?[1-9]|1[012]|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|march|april|june|july|august|september|october|november|december`,
atoi: func(s string) int { atoi: func(s string) int {
return monthTokens[s] return monthTokens[s]
}, },
@ -161,7 +163,7 @@ var (
min: 0, min: 0,
max: 6, max: 6,
defaultList: genericDefaultList[0:7], defaultList: genericDefaultList[0:7],
valuePattern: `[0-7]|sun|mon|tue|wed|thu|fri|sat|sunday|monday|tuesday|wednesday|thursday|friday|saturday`, valuePattern: `0?[0-7]|sun|mon|tue|wed|thu|fri|sat|sunday|monday|tuesday|wednesday|thursday|friday|saturday`,
atoi: func(s string) int { atoi: func(s string) int {
return dowTokens[s] return dowTokens[s]
}, },
@ -193,6 +195,7 @@ var (
fieldFinder = regexp.MustCompile(`\S+`) fieldFinder = regexp.MustCompile(`\S+`)
entryFinder = regexp.MustCompile(`[^,]+`) entryFinder = regexp.MustCompile(`[^,]+`)
layoutRegexp = make(map[string]*regexp.Regexp) layoutRegexp = make(map[string]*regexp.Regexp)
layoutRegexpLock sync.Mutex
) )
/******************************************************************************/ /******************************************************************************/
@ -445,6 +448,9 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error)
directive.first = desc.min directive.first = desc.min
directive.last = desc.max directive.last = desc.max
directive.step = atoi(snormal[pairs[2]:pairs[3]]) directive.step = atoi(snormal[pairs[2]:pairs[3]])
if directive.step < 1 || directive.step > desc.max {
return nil, fmt.Errorf("invalid interval %s", snormal)
}
directives = append(directives, &directive) directives = append(directives, &directive)
continue continue
} }
@ -455,6 +461,9 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error)
directive.first = desc.atoi(snormal[pairs[2]:pairs[3]]) directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
directive.last = desc.max directive.last = desc.max
directive.step = atoi(snormal[pairs[4]:pairs[5]]) directive.step = atoi(snormal[pairs[4]:pairs[5]])
if directive.step < 1 || directive.step > desc.max {
return nil, fmt.Errorf("invalid interval %s", snormal)
}
directives = append(directives, &directive) directives = append(directives, &directive)
continue continue
} }
@ -465,6 +474,9 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error)
directive.first = desc.atoi(snormal[pairs[2]:pairs[3]]) directive.first = desc.atoi(snormal[pairs[2]:pairs[3]])
directive.last = desc.atoi(snormal[pairs[4]:pairs[5]]) directive.last = desc.atoi(snormal[pairs[4]:pairs[5]])
directive.step = atoi(snormal[pairs[6]:pairs[7]]) directive.step = atoi(snormal[pairs[6]:pairs[7]])
if directive.step < 1 || directive.step > desc.max {
return nil, fmt.Errorf("invalid interval %s", snormal)
}
directives = append(directives, &directive) directives = append(directives, &directive)
continue continue
} }
@ -478,6 +490,9 @@ func genericFieldParse(s string, desc fieldDescriptor) ([]*cronDirective, error)
/******************************************************************************/ /******************************************************************************/
func makeLayoutRegexp(layout, value string) *regexp.Regexp { func makeLayoutRegexp(layout, value string) *regexp.Regexp {
layoutRegexpLock.Lock()
defer layoutRegexpLock.Unlock()
layout = strings.Replace(layout, `%value%`, value, -1) layout = strings.Replace(layout, `%value%`, value, -1)
re := layoutRegexp[layout] re := layoutRegexp[layout]
if re == nil { if re == nil {

View File

@ -10,15 +10,13 @@
* *
*/ */
package cronexpr_test package cronexpr
/******************************************************************************/ /******************************************************************************/
import ( import (
"testing" "testing"
"time" "time"
"github.com/gorhill/cronexpr"
) )
/******************************************************************************/ /******************************************************************************/
@ -203,9 +201,9 @@ func TestExpressions(t *testing.T) {
for _, test := range crontests { for _, test := range crontests {
for _, times := range test.times { for _, times := range test.times {
from, _ := time.Parse("2006-01-02 15:04:05", times.from) from, _ := time.Parse("2006-01-02 15:04:05", times.from)
expr, err := cronexpr.Parse(test.expr) expr, err := Parse(test.expr)
if err != nil { if err != nil {
t.Errorf(`cronexpr.Parse("%s") returned "%s"`, test.expr, err.Error()) t.Errorf(`Parse("%s") returned "%s"`, test.expr, err.Error())
} }
next := expr.Next(from) next := expr.Next(from)
nextstr := next.Format(test.layout) nextstr := next.Format(test.layout)
@ -220,17 +218,17 @@ func TestExpressions(t *testing.T) {
func TestZero(t *testing.T) { func TestZero(t *testing.T) {
from, _ := time.Parse("2006-01-02", "2013-08-31") from, _ := time.Parse("2006-01-02", "2013-08-31")
next := cronexpr.MustParse("* * * * * 1980").Next(from) next := MustParse("* * * * * 1980").Next(from)
if next.IsZero() == false { if next.IsZero() == false {
t.Error(`("* * * * * 1980").Next("2013-08-31").IsZero() returned 'false', expected 'true'`) t.Error(`("* * * * * 1980").Next("2013-08-31").IsZero() returned 'false', expected 'true'`)
} }
next = cronexpr.MustParse("* * * * * 2050").Next(from) next = MustParse("* * * * * 2050").Next(from)
if next.IsZero() == true { if next.IsZero() == true {
t.Error(`("* * * * * 2050").Next("2013-08-31").IsZero() returned 'true', expected 'false'`) t.Error(`("* * * * * 2050").Next("2013-08-31").IsZero() returned 'true', expected 'false'`)
} }
next = cronexpr.MustParse("* * * * * 2099").Next(time.Time{}) next = MustParse("* * * * * 2099").Next(time.Time{})
if next.IsZero() == false { if next.IsZero() == false {
t.Error(`("* * * * * 2014").Next(time.Time{}).IsZero() returned 'true', expected 'false'`) t.Error(`("* * * * * 2014").Next(time.Time{}).IsZero() returned 'true', expected 'false'`)
} }
@ -247,7 +245,7 @@ func TestNextN(t *testing.T) {
"Sat, 29 Nov 2014 00:00:00", "Sat, 29 Nov 2014 00:00:00",
} }
from, _ := time.Parse("2006-01-02 15:04:05", "2013-09-02 08:44:30") from, _ := time.Parse("2006-01-02 15:04:05", "2013-09-02 08:44:30")
result := cronexpr.MustParse("0 0 * * 6#5").NextN(from, uint(len(expected))) result := MustParse("0 0 * * 6#5").NextN(from, uint(len(expected)))
if len(result) != len(expected) { if len(result) != len(expected) {
t.Errorf(`MustParse("0 0 * * 6#5").NextN("2013-09-02 08:44:30", 5):\n"`) t.Errorf(`MustParse("0 0 * * 6#5").NextN("2013-09-02 08:44:30", 5):\n"`)
t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result)) t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result))
@ -270,7 +268,7 @@ func TestNextN_every5min(t *testing.T) {
"Mon, 2 Sep 2013 09:05:00", "Mon, 2 Sep 2013 09:05:00",
} }
from, _ := time.Parse("2006-01-02 15:04:05", "2013-09-02 08:44:32") from, _ := time.Parse("2006-01-02 15:04:05", "2013-09-02 08:44:32")
result := cronexpr.MustParse("*/5 * * * *").NextN(from, uint(len(expected))) result := MustParse("*/5 * * * *").NextN(from, uint(len(expected)))
if len(result) != len(expected) { if len(result) != len(expected) {
t.Errorf(`MustParse("*/5 * * * *").NextN("2013-09-02 08:44:30", 5):\n"`) t.Errorf(`MustParse("*/5 * * * *").NextN("2013-09-02 08:44:30", 5):\n"`)
t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result)) t.Errorf(` Expected %d returned time values but got %d instead`, len(expected), len(result))
@ -284,6 +282,29 @@ func TestNextN_every5min(t *testing.T) {
} }
} }
// Issue: https://github.com/gorhill/cronexpr/issues/16
func TestInterval_Interval60Issue(t *testing.T) {
_, err := Parse("*/60 * * * * *")
if err == nil {
t.Errorf("parsing with interval 60 should return err")
}
_, err = Parse("*/61 * * * * *")
if err == nil {
t.Errorf("parsing with interval 61 should return err")
}
_, err = Parse("2/60 * * * * *")
if err == nil {
t.Errorf("parsing with interval 60 should return err")
}
_, err = Parse("2-20/61 * * * * *")
if err == nil {
t.Errorf("parsing with interval 60 should return err")
}
}
/******************************************************************************/ /******************************************************************************/
var benchmarkExpressions = []string{ var benchmarkExpressions = []string{
@ -299,14 +320,14 @@ var benchmarkExpressionsLen = len(benchmarkExpressions)
func BenchmarkParse(b *testing.B) { func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = cronexpr.MustParse(benchmarkExpressions[i%benchmarkExpressionsLen]) _ = MustParse(benchmarkExpressions[i%benchmarkExpressionsLen])
} }
} }
func BenchmarkNext(b *testing.B) { func BenchmarkNext(b *testing.B) {
exprs := make([]*cronexpr.Expression, benchmarkExpressionsLen) exprs := make([]*Expression, benchmarkExpressionsLen)
for i := 0; i < benchmarkExpressionsLen; i++ { for i := 0; i < benchmarkExpressionsLen; i++ {
exprs[i] = cronexpr.MustParse(benchmarkExpressions[i]) exprs[i] = MustParse(benchmarkExpressions[i])
} }
from := time.Now() from := time.Now()
b.ResetTimer() b.ResetTimer()

View File

@ -8,15 +8,13 @@
* *
*/ */
package cronexpr_test package cronexpr
/******************************************************************************/ /******************************************************************************/
import ( import (
"fmt" "fmt"
"time" "time"
"github.com/gorhill/cronexpr"
) )
/******************************************************************************/ /******************************************************************************/
@ -24,7 +22,7 @@ import (
// ExampleMustParse // ExampleMustParse
func ExampleMustParse() { func ExampleMustParse() {
t := time.Date(2013, time.August, 31, 0, 0, 0, 0, time.UTC) t := time.Date(2013, time.August, 31, 0, 0, 0, 0, time.UTC)
nextTimes := cronexpr.MustParse("0 0 29 2 *").NextN(t, 5) nextTimes := MustParse("0 0 29 2 *").NextN(t, 5)
for i := range nextTimes { for i := range nextTimes {
fmt.Println(nextTimes[i].Format(time.RFC1123)) fmt.Println(nextTimes[i].Format(time.RFC1123))
// Output: // Output: