support cron

master
兔子 1 year ago
parent c1295d9e26
commit d41b1134c9

@ -2,10 +2,80 @@ package startimer
import (
"errors"
"strconv"
"strings"
"sync"
"time"
)
func parseCron(cron string) (StarTimer, error) {
var rpmonths = map[string]int{
"JAN": 1,
"FEB": 2,
"MAR": 3,
"APR": 4,
"MAY": 5,
"JUN": 6,
"JUL": 7,
"AUG": 8,
"SEP": 9,
"OCT": 10,
"NOV": 11,
"DEC": 12,
}
var rpweekdays = map[string]int{
"SUN": 0,
"MON": 1,
"TUE": 2,
"WED": 3,
"THU": 4,
"FRI": 5,
"SAT": 6,
}
func NewTimerWithCron(cron ...string) (StarTimer, error) {
tmr := StarTimer{
base: time.Now(),
nextDate: time.Time{},
mu: new(sync.RWMutex),
}
for _, c := range cron {
c = "0 " + c
rpt, err := parseCron(c)
if err != nil {
return tmr, err
}
tmr.repeat = append(tmr.repeat, rpt)
}
return tmr, nil
}
func NewTimerWithSecCron(cron ...string) (StarTimer, error) {
tmr := StarTimer{
base: time.Now(),
nextDate: time.Time{},
mu: new(sync.RWMutex),
}
for _, c := range cron {
rpt, err := parseCron(c)
if err != nil {
return tmr, err
}
tmr.repeat = append(tmr.repeat, rpt)
}
return tmr, nil
}
func parseCron(cron string) (*Repeats, error) {
{
cron = strings.ToUpper(cron)
for k, v := range rpmonths {
cron = strings.ReplaceAll(cron, k, strconv.Itoa(v))
}
for k, v := range rpweekdays {
cron = strings.ReplaceAll(cron, k, strconv.Itoa(v))
}
}
for {
oldLen := len(cron)
cron = strings.ReplaceAll(strings.TrimSpace(cron), " ", " ")
@ -15,8 +85,110 @@ func parseCron(cron string) (StarTimer, error) {
}
ct := strings.Split(cron, " ")
if len(ct) != 6 {
return StarTimer{}, errors.New("Invalid cron,argument not enough")
return nil, errors.New("Invalid cron,argument not enough")
}
foundFirstAll := false
var myMap = make(map[Unit]map[uint32]Repeat)
for idx, c := range ct {
if _, ok := myMap[Unit(idx)]; !ok {
myMap[Unit(idx)] = make(map[uint32]Repeat)
}
var minVal, maxVal int
switch Unit(idx) {
case STAR_MINUTE, STAR_SECOND:
minVal = 0
maxVal = 60
case STAR_HOUR:
minVal = 0
maxVal = 24
case STAR_DAY:
minVal = 1
maxVal = 32
case STAR_MONTH:
minVal = 1
maxVal = 13
case STAR_WEEK:
minVal = 0
maxVal = 7
}
cdt := strings.Split(c, ",")
for _, dtl := range cdt {
dtl = strings.TrimSpace(dtl)
if dtl == "*" {
if !foundFirstAll {
foundFirstAll = true
if maxVal > 0 {
for i := minVal; i < maxVal; i++ {
val := uint32(i)
myMap[Unit(idx)][val] = Repeat{
Unit: Unit(idx),
Value: val,
}
}
}
}
continue
}
if strings.Contains(dtl, "*/") {
num, err := strconv.Atoi(strings.TrimPrefix(dtl, "*/"))
if err != nil {
return nil, err
}
for i := minVal; i < maxVal; i++ {
val := i
if Unit(idx) == STAR_DAY || Unit(idx) == STAR_MONTH {
val--
}
if val%num == 0 {
myMap[Unit(idx)][uint32(val)] = Repeat{
Unit: Unit(idx),
Value: uint32(i),
}
}
}
continue
}
if strings.Contains(dtl, "-") {
numbers := strings.Split(dtl, "-")
if len(numbers) != 2 {
return nil, errors.New("Invalid Cron")
}
startNum, err := strconv.Atoi(numbers[0])
if err != nil {
return nil, err
}
endNum, err := strconv.Atoi(numbers[1])
if err != nil {
return nil, err
}
if startNum < minVal && endNum >= maxVal {
return nil, errors.New("Invalid Cron")
}
for i := startNum; i <= endNum; i++ {
myMap[Unit(idx)][uint32(i)] = Repeat{
Unit: Unit(idx),
Value: uint32(i),
}
}
continue
}
number, err := strconv.Atoi(dtl)
if err != nil {
return nil, err
}
myMap[Unit(idx)][uint32(number)] = Repeat{
Unit: Unit(idx),
Value: uint32(number),
}
}
}
var repeat Repeats
for _, v := range myMap {
for _, rpt := range v {
repeat.Repeat = append(repeat.Repeat, rpt)
}
}
return StarTimer{}, nil
return &repeat, nil
}

@ -151,3 +151,47 @@ func TestPrepareCronSimple(t *testing.T) {
fmt.Println("")
}
}
func TestInvalidCron(t *testing.T) {
tk := StarTimer{
base: time.Now(),
repeat: []*Repeats{
{
Every: false,
Repeat: []Repeat{
{Unit: STAR_MINUTE, Value: 0},
{Unit: STAR_HOUR, Value: 2},
{Unit: STAR_DAY, Value: 29}, {Unit: STAR_DAY, Value: 30}, {Unit: STAR_DAY, Value: 31},
{Unit: STAR_MONTH, Value: 4},
},
},
},
}
base := tk.base
for i := 0; i < 10; i++ {
base = tk.parseNextDate(base, true)
fmt.Println(base)
fmt.Println("")
}
}
func TestParseCronMax(t *testing.T) {
tk, err := NewTimerWithSecCron("*/5 40-59 * * * *")
if err != nil {
t.Fatal(err)
}
tk.AddTask(func() {
fmt.Println("现在是", time.Now())
})
for i := 0; i < 20; i++ {
fmt.Println()
}
/*
err = tk.Run()
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second * 300)
*/
}

@ -134,6 +134,9 @@ func (t *StarTimer) ImportRepeats(r string) error {
func (t *StarTimer) BaseDate() time.Time {
return t.base
}
func (t *StarTimer) SetBaseDate(date time.Time) {
t.base = date
}
func (t *StarTimer) ResetWithRepeat(base time.Time, repeat []*Repeats) error {
t.Stop()
@ -142,6 +145,10 @@ func (t *StarTimer) ResetWithRepeat(base time.Time, repeat []*Repeats) error {
return t.Run()
}
func (t *StarTimer) Repeats() []*Repeats {
return t.repeat
}
func (t *StarTimer) Run() error {
t.mu.Lock()
defer t.mu.Unlock()
@ -192,7 +199,7 @@ func (t *StarTimer) parseNextDate(base time.Time, isMock bool) time.Time {
if d.Every {
dates = append(dates, t.parseEveryNextDate(base, d, isMock)...)
} else {
dates = append(dates, t.parseStaticNextDate(base, d))
dates = append(dates, t.parseStaticNextDate(base, d, false))
}
}
sort.SliceStable(dates, func(i, j int) bool {
@ -215,7 +222,8 @@ func (t *StarTimer) parseNextDate(base time.Time, isMock bool) time.Time {
return time.Time{}
}
func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats, recall bool) time.Time {
base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), base.Second(), 0, base.Location())
var targets []time.Time
var uniqueRepeat [][]Repeat
selectMap := make(map[Unit][]Repeat)
@ -249,7 +257,8 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
return task[i].Unit < task[j].Unit
})
target := base
if !r.Every { //固定日期
veryDay, veryMonth := 0, 0 //验证日期
if !r.Every { //固定日期
for _, d := range task {
switch d.Unit {
case STAR_SECOND:
@ -271,6 +280,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
}
target = target.Add(time.Hour * time.Duration(sub))
case STAR_DAY:
veryDay = int(d.Value)
sub := int(d.Value) - target.Day()
if sub >= 0 {
target = target.Add(time.Hour * 24 * time.Duration(sub))
@ -278,6 +288,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
}
target = time.Date(target.Year(), target.Month()+1, int(d.Value), target.Hour(), target.Minute(), target.Second(), 0, target.Location())
case STAR_MONTH:
veryMonth = int(d.Value)
sub := int(d.Value) - int(target.Month())
if sub < 0 {
sub += 12
@ -292,8 +303,10 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
}
}
}
if target == base {
if (veryDay != 0 && target.Day() != veryDay) || (veryMonth != 0 && int(target.Month()) != veryMonth) {
continue
}
if target == base && !recall {
continue
}
targets = append(targets, target)
@ -303,7 +316,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
return targets[i].UnixNano() < targets[j].UnixNano()
})
for k, v := range targets {
if v.After(base) {
if v.UnixNano() > base.UnixNano() || (recall && v.UnixNano() == base.UnixNano()) {
targets = targets[k:]
break
}
@ -315,7 +328,8 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time {
return targets[0]
}
}
return t.parseStaticNextDate(targets[0].Add(time.Hour*24), r)
nextBase := time.Date(targets[0].Year(), targets[0].Month(), targets[0].Day(), 0, 0, 0, 0, targets[0].Location())
return t.parseStaticNextDate(nextBase.Add(time.Hour*24), r, true)
}
}
if len(targets) > 0 {

@ -14,8 +14,8 @@ const (
STAR_HOUR
STAR_DAY
STAR_MONTH
STAR_YEAR
STAR_WEEK
STAR_YEAR
)
type Repeats struct {

Loading…
Cancel
Save