Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f83fd9f635 | ||
|
0f83cb5e66 | ||
9d57144934 | |||
b54c0cf45c | |||
d148ea2445 | |||
7342ef6605 | |||
|
cc9071bc70 | ||
5d7d9fc547 | |||
|
d9a422dfec | ||
|
40683dc466 | ||
b7f0209cc3 | |||
ed2dfe0809 | |||
ed4ddb00ed | |||
f8e7d16241 | |||
74fddef18d | |||
1140087c45 | |||
2904c8f38c | |||
0dce1192d2 | |||
|
19a4034928 | ||
|
539e764c08 | ||
|
cc37d62a2d | ||
316d616419 | |||
|
f3702f9df0 |
1
condition.go
Normal file
1
condition.go
Normal file
|
@ -0,0 +1 @@
|
|||
package crontab
|
445
crontab.go
445
crontab.go
|
@ -4,46 +4,49 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clinked "474420502.top/eson/structure/circular_linked"
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"474420502.top/eson/structure/circular_linked"
|
||||
"github.com/Pallinder/go-randomdata"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
type randLR struct {
|
||||
left, right int
|
||||
// StatusType 设置状态的类型
|
||||
type StatusType int
|
||||
|
||||
const (
|
||||
_ StatusType = iota
|
||||
// SExecuteOK 设置这个次成功或者失败的状态
|
||||
SExecuteOK
|
||||
// SExecuteSleep 设置下次Sleep时间, 只影响一次
|
||||
SExecuteSleep
|
||||
// SExecuteCrontab 设置改写Crontab, 覆盖以前的Crontab
|
||||
SExecuteCrontab
|
||||
)
|
||||
|
||||
// Force 强制下次执行
|
||||
type Force struct {
|
||||
sleep time.Duration
|
||||
}
|
||||
|
||||
type hInterval struct {
|
||||
PlanFail []randLR
|
||||
PlanNormal []randLR
|
||||
|
||||
Count int
|
||||
ConstCount int
|
||||
}
|
||||
|
||||
func (interval *hInterval) reset() {
|
||||
interval.Count = interval.ConstCount
|
||||
}
|
||||
|
||||
type timePointer struct {
|
||||
left, right int
|
||||
leftlimit, rightlimit int
|
||||
per int
|
||||
isAll bool
|
||||
}
|
||||
|
||||
func (tp *timePointer) String() string {
|
||||
return fmt.Sprintf("left: %d, right: %d, leftlimit: %d, rightlimit: %d, per: %d", tp.left, tp.right, tp.leftlimit, tp.rightlimit, tp.per)
|
||||
// NextTime 下次执行时间
|
||||
func (force *Force) NextTime() time.Time {
|
||||
return time.Now().Add(force.sleep)
|
||||
}
|
||||
|
||||
// Crontab 的string解析
|
||||
type Crontab struct {
|
||||
crontab string
|
||||
|
||||
uid uuid.UUID
|
||||
force *Force
|
||||
|
||||
min []timePointer
|
||||
hour []timePointer
|
||||
day []timePointer
|
||||
|
@ -55,35 +58,76 @@ type Crontab struct {
|
|||
|
||||
YearPlan *trieYear
|
||||
|
||||
interval *clinked.CircularLinked
|
||||
lastStatus bool
|
||||
nextTime time.Time
|
||||
interval *clinked.CircularLinked
|
||||
lastStatus bool
|
||||
isCalculated bool
|
||||
|
||||
trueCount int
|
||||
failCount int
|
||||
nextTime time.Time
|
||||
}
|
||||
|
||||
// NewCrontab create 一个crontab
|
||||
func NewCrontab(crontab string) *Crontab {
|
||||
cron := &Crontab{}
|
||||
cron.FromString(crontab)
|
||||
cron.crontab = strings.TrimSpace(crontab)
|
||||
cron.FromString(cron.crontab)
|
||||
return cron
|
||||
}
|
||||
|
||||
// SetStatus 设置状态 接口定义
|
||||
func (cron *Crontab) SetStatus(status interface{}) {
|
||||
|
||||
if cron.interval != nil {
|
||||
cron.lastStatus = status.(bool)
|
||||
// UnmarshalYAML 添加序列化接口 Marshal没实现, 需要的时候可以自己添加
|
||||
func (cron *Crontab) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var buf string
|
||||
err := unmarshal(&buf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatus 设置状态 接口定义
|
||||
func (cron *Crontab) GetStatus() (status interface{}) {
|
||||
if cron.interval != nil {
|
||||
return cron.lastStatus
|
||||
if err := cron.FromString(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimeUp 是否时间快到
|
||||
// SetStatus 设置上次状态 true false
|
||||
func (cron *Crontab) SetStatus(statusType StatusType, statusValue ...interface{}) {
|
||||
|
||||
switch statusType {
|
||||
case SExecuteOK:
|
||||
if cron.interval != nil {
|
||||
cron.lastStatus = statusValue[0].(bool)
|
||||
}
|
||||
case SExecuteSleep:
|
||||
force := new(Force)
|
||||
ivalue := statusValue[0]
|
||||
switch value := ivalue.(type) {
|
||||
case int:
|
||||
force.sleep = time.Duration(value) * time.Second
|
||||
case int64:
|
||||
force.sleep = time.Duration(value) * time.Second
|
||||
case time.Duration:
|
||||
force.sleep = value
|
||||
default:
|
||||
panic(errors.New("statusValue type is error, the type is" + reflect.TypeOf(ivalue).String()))
|
||||
}
|
||||
cron.force = force
|
||||
case SExecuteCrontab:
|
||||
crontab := statusValue[0].(string)
|
||||
cron.crontab = strings.TrimSpace(crontab)
|
||||
cron.FromString(cron.crontab)
|
||||
default:
|
||||
panic(errors.New("StatusType is unknown, the type " + reflect.TypeOf(statusType).String()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// // GetStatus 获取上次状态 true false
|
||||
// func (cron *Crontab) GetStatus() (status bool) {
|
||||
// return cron.lastStatus
|
||||
// }
|
||||
|
||||
// TimeUp 是否时间快到, 时间间隔调用完TimeUp后必须调用NextTime, 为了精准控制时间
|
||||
func (cron *Crontab) TimeUp() bool {
|
||||
|
||||
if cron.interval != nil {
|
||||
|
@ -94,16 +138,27 @@ func (cron *Crontab) TimeUp() bool {
|
|||
}
|
||||
|
||||
// NextTime 返回下次任务的时间
|
||||
func (cron *Crontab) NextTime() *time.Time {
|
||||
func (cron *Crontab) NextTime() time.Time {
|
||||
|
||||
if cron.force != nil {
|
||||
nt := cron.force.NextTime()
|
||||
cron.force = nil
|
||||
return nt
|
||||
}
|
||||
|
||||
if cron.interval != nil {
|
||||
return &cron.nextTime
|
||||
if !cron.isCalculated {
|
||||
now := time.Now()
|
||||
cron.intervalCalculateNextTime(now)
|
||||
}
|
||||
return cron.nextTime
|
||||
}
|
||||
|
||||
if len(cron.WillPlans) > 0 {
|
||||
return &cron.WillPlans[0]
|
||||
return cron.WillPlans[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
return time.Now().Add(time.Second * 2)
|
||||
}
|
||||
|
||||
func (cron *Crontab) String() string {
|
||||
|
@ -112,15 +167,36 @@ func (cron *Crontab) String() string {
|
|||
|
||||
// FromString 解析crontab 的 表达式
|
||||
func (cron *Crontab) FromString(crontab string) error {
|
||||
crontab = strings.TrimSpace(crontab)
|
||||
crontab = cron.crontab
|
||||
|
||||
uid, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cron.uid = uid
|
||||
|
||||
cron.interval = nil
|
||||
cron.min = nil
|
||||
cron.hour = nil
|
||||
cron.day = nil
|
||||
cron.month = nil
|
||||
cron.week = nil
|
||||
|
||||
cron.WillPlans = nil
|
||||
cron.SkipPlans = nil
|
||||
|
||||
cron.YearPlan = nil
|
||||
|
||||
matches := regexp.MustCompile("[^ ]+").FindAllString(crontab, -1)
|
||||
mlen := len(matches)
|
||||
switch mlen {
|
||||
case 1:
|
||||
// "f1-2|5-10x5,f1|10m,10-15,f1"
|
||||
cron.nextTime = time.Now()
|
||||
|
||||
cron.lastStatus = true
|
||||
cron.isCalculated = true
|
||||
cron.nextTime = time.Now()
|
||||
|
||||
cron.interval = clinked.NewCircularLinked()
|
||||
var intervalList []interface{}
|
||||
intervalList = parseIntervalString(matches[0])
|
||||
|
@ -134,6 +210,7 @@ func (cron *Crontab) FromString(crontab string) error {
|
|||
cron.week = createTimePointer(matches[4], 0, 6, true)
|
||||
|
||||
cron.createYearPlan()
|
||||
cron.TimeUp()
|
||||
default:
|
||||
return errors.New("mathches len != want, check crontab string")
|
||||
}
|
||||
|
@ -153,7 +230,7 @@ func (cron *Crontab) linuxTimeUp() bool {
|
|||
createlen := 500
|
||||
|
||||
plen := len(cron.WillPlans)
|
||||
if plen <= createlen {
|
||||
if plen <= createlen { // 如果当前生成的计划表少于 限制的500. 就生成新的计划表
|
||||
var lastplan time.Time
|
||||
if plen == 0 {
|
||||
lastplan = now
|
||||
|
@ -171,9 +248,13 @@ func (cron *Crontab) linuxTimeUp() bool {
|
|||
if len(cron.WillPlans) > 0 {
|
||||
istimeup := false
|
||||
for i := 0; i < maxlen; i++ {
|
||||
|
||||
// 统计过了多少计划任务时间表 i - 1
|
||||
if now.Unix() >= cron.WillPlans[i].Unix() {
|
||||
istimeup = true
|
||||
} else {
|
||||
|
||||
// 清除过时的计划任务时间表
|
||||
if istimeup {
|
||||
if i-1 > 0 {
|
||||
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...)
|
||||
|
@ -188,7 +269,7 @@ func (cron *Crontab) linuxTimeUp() bool {
|
|||
return istimeup
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有计划表都不符合, 全部放到忽略的计划表上, 这个表方便打印查看, 因为程序执行超时过了多少计划任务
|
||||
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...)
|
||||
cron.WillPlans = nil
|
||||
return istimeup
|
||||
|
@ -198,215 +279,73 @@ func (cron *Crontab) linuxTimeUp() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IntervalCalculateNextTime 计算时间间隔的下次时间
|
||||
func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
|
||||
iv := cron.interval.Cursor().GetValue().(*hInterval)
|
||||
isecond := 0
|
||||
|
||||
if iv.PlanFailCount.Size() == 0 && len(iv.PlanFail) == 0 {
|
||||
cron.lastStatus = true
|
||||
}
|
||||
|
||||
if cron.lastStatus {
|
||||
|
||||
cron.trueCount++
|
||||
cron.failCount = 0
|
||||
|
||||
isecond = intervalPriorityListISecond(&iv.PlanTrueCount, cron.trueCount)
|
||||
if isecond == -1 {
|
||||
if len(iv.PlanTrue) > 0 {
|
||||
idx := randomdata.Number(len(iv.PlanTrue))
|
||||
lr := iv.PlanTrue[idx]
|
||||
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||
|
||||
} else {
|
||||
isecond = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fmt.Println(time.Now().Format("01-02 15:04:05"), cron.uid.String(), "success:", cron.trueCount, " wait:", isecond)
|
||||
|
||||
} else {
|
||||
|
||||
cron.failCount++
|
||||
cron.trueCount = 0
|
||||
|
||||
isecond = intervalPriorityListISecond(&iv.PlanFailCount, cron.failCount)
|
||||
if isecond == -1 {
|
||||
if len(iv.PlanFail) > 0 {
|
||||
idx := randomdata.Number(len(iv.PlanFail))
|
||||
lr := iv.PlanFail[idx]
|
||||
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||
} else {
|
||||
isecond = 0
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(time.Now().Format("01-02 15:04:05"), cron.uid.String(), "fail:", cron.failCount, " wait:", isecond)
|
||||
}
|
||||
|
||||
iv.Count--
|
||||
if iv.Count <= 0 {
|
||||
iv.reset()
|
||||
cron.interval.MoveNext()
|
||||
}
|
||||
|
||||
cron.isCalculated = true
|
||||
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
|
||||
}
|
||||
|
||||
func (cron *Crontab) intervalTimeUp() bool {
|
||||
|
||||
now := time.Now()
|
||||
if now.Unix() >= cron.nextTime.Unix() {
|
||||
|
||||
iv := cron.interval.Cursor().GetValue().(hInterval)
|
||||
isecond := 0
|
||||
if cron.lastStatus == false && len(iv.PlanFail) > 0 {
|
||||
idx := randomdata.Number(len(iv.PlanFail))
|
||||
lr := iv.PlanFail[idx]
|
||||
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||
} else {
|
||||
idx := randomdata.Number(len(iv.PlanNormal))
|
||||
lr := iv.PlanNormal[idx]
|
||||
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||
if cron.isCalculated { // 需要调用nexttime()才能正常正常计算下次的时间
|
||||
now := time.Now()
|
||||
if now.Unix() >= cron.nextTime.Unix() {
|
||||
cron.isCalculated = false
|
||||
return true
|
||||
}
|
||||
|
||||
iv.Count--
|
||||
if iv.Count <= 0 {
|
||||
iv.reset()
|
||||
cron.interval.MoveNext()
|
||||
}
|
||||
|
||||
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func createTimePointer(min string, llimit, rlimit int, fixedLeftRight bool) []timePointer {
|
||||
|
||||
var result []timePointer
|
||||
|
||||
exelist := strings.Split(min, ",")
|
||||
for _, exe := range exelist {
|
||||
tp := timePointer{}
|
||||
|
||||
takeper := strings.Split(exe, "/") // per
|
||||
var rangevalue, per string
|
||||
if len(takeper) == 1 {
|
||||
rangevalue = exe
|
||||
per = "1"
|
||||
} else {
|
||||
rangevalue = takeper[0]
|
||||
per = takeper[1]
|
||||
}
|
||||
// takeRange
|
||||
be := strings.Split(rangevalue, "-")
|
||||
var left, rigth string
|
||||
switch len(be) {
|
||||
case 1:
|
||||
left = be[0]
|
||||
rigth = be[0]
|
||||
case 2:
|
||||
left = be[0]
|
||||
rigth = be[1]
|
||||
default:
|
||||
panic(errors.New("range value is > 2"))
|
||||
}
|
||||
|
||||
if left == "*" {
|
||||
tp.left = llimit
|
||||
} else {
|
||||
ileft, err := strconv.Atoi(strings.Replace(left, "^", "-", -1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tp.left = ileft
|
||||
}
|
||||
|
||||
if rigth == "*" {
|
||||
tp.right = rlimit
|
||||
} else {
|
||||
iright, err := strconv.Atoi(strings.Replace(rigth, "^", "-", -1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tp.right = iright
|
||||
}
|
||||
|
||||
iper, err := strconv.Atoi(per)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tp.per = iper
|
||||
tp.leftlimit = llimit
|
||||
tp.rightlimit = rlimit
|
||||
|
||||
// 修正左值
|
||||
leftfixed := tp.left
|
||||
if leftfixed < 0 {
|
||||
leftfixed += tp.rightlimit + 1
|
||||
if fixedLeftRight {
|
||||
tp.left = leftfixed
|
||||
}
|
||||
}
|
||||
|
||||
rightfixed := tp.right
|
||||
if rightfixed < 0 {
|
||||
rightfixed += tp.rightlimit + 1
|
||||
if fixedLeftRight {
|
||||
tp.right = rightfixed
|
||||
}
|
||||
}
|
||||
|
||||
// 全部符合 当左等于左 且 右等于右最大 并且 per == 1 TODO: 如果加入时间间隔 就需要 加多一个 附加值
|
||||
if leftfixed == tp.leftlimit && rightfixed == tp.rightlimit && tp.per == 1 {
|
||||
tp.isAll = true
|
||||
}
|
||||
|
||||
result = append(result, tp)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseIntervalString(crontab string) []interface{} {
|
||||
var result []interface{}
|
||||
|
||||
values := strings.Split(crontab, ",")
|
||||
for _, value := range values {
|
||||
interval := hInterval{}
|
||||
// 次数
|
||||
valuesCounts := strings.Split(value, "x")
|
||||
switch len(valuesCounts) {
|
||||
case 1:
|
||||
interval.ConstCount = 1
|
||||
|
||||
case 2:
|
||||
count, err := strconv.Atoi(valuesCounts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
interval.ConstCount = count
|
||||
default:
|
||||
panic("valuesCounts error, the len is not in range")
|
||||
}
|
||||
|
||||
// 统计失败与普通间隔值的数组
|
||||
failAndNormal := valuesCounts[0]
|
||||
|
||||
valuesFN := strings.Split(failAndNormal, "|")
|
||||
|
||||
for _, FN := range valuesFN {
|
||||
if FN == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch FN[0] {
|
||||
case 'f', 'F':
|
||||
fvalue := FN[1:]
|
||||
interval.PlanFail = append(interval.PlanFail, parseRandLR(fvalue))
|
||||
case 'n':
|
||||
value := FN[1:]
|
||||
interval.PlanNormal = append(interval.PlanNormal, parseRandLR(value))
|
||||
default:
|
||||
interval.PlanNormal = append(interval.PlanNormal, parseRandLR(FN))
|
||||
}
|
||||
}
|
||||
|
||||
interval.reset()
|
||||
result = append(result, interval)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseRandLR(value string) randLR {
|
||||
vlen := len(value)
|
||||
lastchar := value[vlen-1]
|
||||
|
||||
lr := strings.Split(value, "-")
|
||||
switch len(lr) {
|
||||
case 1:
|
||||
lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[0], lastchar)}
|
||||
return lr
|
||||
case 2:
|
||||
|
||||
lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[1], lastchar)}
|
||||
return lr
|
||||
default:
|
||||
panic("lr is error")
|
||||
}
|
||||
}
|
||||
|
||||
func getInt(v string) int {
|
||||
vint, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return vint
|
||||
}
|
||||
|
||||
func parseTimeValue(v string, lastchar byte) int {
|
||||
|
||||
vlen := len(v)
|
||||
|
||||
switch lastchar {
|
||||
case 's':
|
||||
return getInt(v[:vlen-1])
|
||||
case 'm':
|
||||
return getInt(v[:vlen-1]) * 60
|
||||
case 'h':
|
||||
return getInt(v[:vlen-1]) * 3600
|
||||
case 'd':
|
||||
return getInt(v[:vlen-1]) * 3600 * 24
|
||||
default:
|
||||
return getInt(v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ package crontab
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// type LRValue struct {
|
||||
|
@ -26,8 +29,8 @@ func TestParseCrontab(t *testing.T) {
|
|||
}
|
||||
|
||||
cron.createYearPlan()
|
||||
if !cron.TimeUp() {
|
||||
t.Error("timeup error")
|
||||
if cron.TimeUp() {
|
||||
t.Error("timeup error", cron.WillPlans[0:2])
|
||||
}
|
||||
|
||||
PrintMemUsage()
|
||||
|
@ -36,13 +39,13 @@ func TestParseCrontab(t *testing.T) {
|
|||
|
||||
func TestParseInterval(t *testing.T) {
|
||||
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1"
|
||||
crontab := "2"
|
||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f22?15-12"
|
||||
crontab := "2s"
|
||||
cron := NewCrontab(crontab)
|
||||
now := time.Now()
|
||||
for i := 0; i <= 6; i++ {
|
||||
for i := 0; i <= 3; i++ {
|
||||
log.Println(cron.NextTime())
|
||||
if cron.TimeUp() {
|
||||
|
||||
sec := time.Since(now).Seconds()
|
||||
if i != 0 {
|
||||
if 2.0 <= sec && sec <= 2.1 {
|
||||
|
@ -58,13 +61,85 @@ func TestParseInterval(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func isInRangeTime(sec, t float64) error {
|
||||
if sec >= t-0.05 && sec <= t+0.05 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("计算错误 范围 %f-%f interval time is %f", t-0.05, t+0.05, sec)
|
||||
}
|
||||
|
||||
func TestParseIntervalOnline1(t *testing.T) {
|
||||
uid, _ := uuid.NewV4()
|
||||
log.Println(string(uid.Bytes()))
|
||||
|
||||
crontab := "f[c > 2]=3|s[c <= 2]"
|
||||
log.Println(crontab)
|
||||
}
|
||||
|
||||
func TestParseIntervalPlus(t *testing.T) {
|
||||
// crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||
// crondata := NewCrontab("*22 * * * *")
|
||||
|
||||
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1"
|
||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1" ^代表往后算 ^5 2月份就代表 28-4=24 f代表失败后1-2秒设置 由SetStatus控制 t 相反默认不写前缀为t
|
||||
|
||||
log.SetFlags(log.Llongfile)
|
||||
|
||||
crontab := "f=2|f2>=3|f3>=4|1|s4>=5"
|
||||
cron := NewCrontab(crontab)
|
||||
|
||||
i := 0
|
||||
now := time.Now()
|
||||
lasttime := now
|
||||
|
||||
for j := 0; j <= 2500; j++ {
|
||||
|
||||
nexttime := cron.NextTime()
|
||||
if j >= 5 && !nexttime.Equal(lasttime) {
|
||||
lasttime = nexttime
|
||||
}
|
||||
|
||||
if cron.TimeUp() {
|
||||
sec := time.Since(now).Seconds()
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
|
||||
case 1:
|
||||
if err := isInRangeTime(sec, 2.0); err != nil {
|
||||
t.Error(err.Error(), "status = ", cron.lastStatus)
|
||||
}
|
||||
case 2:
|
||||
if err := isInRangeTime(sec, 3.0); err != nil {
|
||||
t.Error(err.Error(), "status = ", cron.lastStatus)
|
||||
}
|
||||
case 3:
|
||||
if err := isInRangeTime(sec, 4.0); err != nil {
|
||||
t.Error(err.Error(), "status = ", cron.lastStatus)
|
||||
}
|
||||
case 5:
|
||||
if err := isInRangeTime(sec, 1); err != nil {
|
||||
t.Error(err.Error(), "status = ", cron.lastStatus)
|
||||
}
|
||||
default:
|
||||
if i >= 10 {
|
||||
if err := isInRangeTime(sec, 5.0); err != nil {
|
||||
t.Error(err.Error(), "status = ", cron.lastStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
if i <= 3 {
|
||||
cron.SetStatus(SExecuteOK, false)
|
||||
} else {
|
||||
cron.SetStatus(SExecuteOK, true)
|
||||
}
|
||||
|
||||
now = time.Now()
|
||||
i++
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
|
||||
func PrintMemUsage() {
|
||||
|
|
205
interval_base.go
Normal file
205
interval_base.go
Normal file
|
@ -0,0 +1,205 @@
|
|||
package crontab
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"474420502.top/eson/structure/priority_list"
|
||||
randomdata "github.com/Pallinder/go-randomdata"
|
||||
)
|
||||
|
||||
type randLR struct {
|
||||
left, right int
|
||||
}
|
||||
|
||||
// NodeCount 用于priority_list
|
||||
type NodeCount struct {
|
||||
plist.Node
|
||||
randLR
|
||||
}
|
||||
|
||||
type hInterval struct {
|
||||
PlanFailCount plist.PriorityList
|
||||
PlanTrueCount plist.PriorityList
|
||||
|
||||
PlanFail []randLR
|
||||
PlanTrue []randLR
|
||||
|
||||
Count int
|
||||
ConstCount int
|
||||
}
|
||||
|
||||
func (interval *hInterval) reset() {
|
||||
interval.Count = interval.ConstCount
|
||||
}
|
||||
|
||||
// Compare NodeCount比较函数
|
||||
func (rlr *NodeCount) Compare(v plist.INode) bool {
|
||||
return rlr.GetValue().(int) > v.GetValue().(int)
|
||||
}
|
||||
|
||||
func parseIntervalFail(interval *hInterval, FN string) {
|
||||
scharIndex := strings.Index(FN, ">")
|
||||
if scharIndex != -1 {
|
||||
|
||||
if FN[scharIndex+1] != '=' {
|
||||
panic(errors.New("= is not exist"))
|
||||
}
|
||||
|
||||
fc := FN[0:scharIndex]
|
||||
flr := FN[scharIndex+2:]
|
||||
|
||||
node := new(NodeCount)
|
||||
node.SetValue(getInt(fc[1:]))
|
||||
node.randLR = parseRandLR(flr)
|
||||
interval.PlanFailCount.Insert(node)
|
||||
} else {
|
||||
|
||||
if FN[1] != '=' {
|
||||
panic(errors.New("= is not exist"))
|
||||
}
|
||||
|
||||
fvalue := FN[2:]
|
||||
interval.PlanFail = append(interval.PlanFail, parseRandLR(fvalue))
|
||||
}
|
||||
}
|
||||
|
||||
func parseIntervalSuccess(interval *hInterval, FN string) {
|
||||
scharIndex := strings.Index(FN, ">")
|
||||
if scharIndex != -1 {
|
||||
if FN[scharIndex+1] != '=' {
|
||||
panic(errors.New("= is not exist"))
|
||||
}
|
||||
|
||||
tc := FN[0:scharIndex]
|
||||
tlr := FN[scharIndex+2:]
|
||||
|
||||
node := new(NodeCount)
|
||||
node.SetValue(getInt(tc[1:]))
|
||||
node.randLR = parseRandLR(tlr)
|
||||
interval.PlanTrueCount.Insert(node)
|
||||
|
||||
} else {
|
||||
|
||||
if FN[1] != '=' {
|
||||
panic(errors.New("= is not exist"))
|
||||
}
|
||||
|
||||
tvalue := FN[2:]
|
||||
interval.PlanTrue = append(interval.PlanTrue, parseRandLR(tvalue))
|
||||
}
|
||||
}
|
||||
|
||||
func parseIntervalString(crontab string) []interface{} {
|
||||
var result []interface{}
|
||||
|
||||
values := strings.Split(crontab, ",")
|
||||
for _, value := range values {
|
||||
interval := &hInterval{}
|
||||
|
||||
// 次数
|
||||
valuesCounts := strings.Split(value, "x")
|
||||
switch len(valuesCounts) {
|
||||
case 1:
|
||||
interval.ConstCount = 1
|
||||
|
||||
case 2:
|
||||
count, err := strconv.Atoi(valuesCounts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
interval.ConstCount = count
|
||||
default:
|
||||
panic("valuesCounts error, the len is not in range")
|
||||
}
|
||||
|
||||
// 统计失败与普通间隔值的数组
|
||||
failAndNormal := valuesCounts[0]
|
||||
|
||||
valuesFN := strings.Split(failAndNormal, "|")
|
||||
|
||||
for _, FN := range valuesFN {
|
||||
if FN == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch FN[0] {
|
||||
case 'f', 'F':
|
||||
parseIntervalFail(interval, FN)
|
||||
case 's', 'S':
|
||||
parseIntervalSuccess(interval, FN)
|
||||
default:
|
||||
FN = "s=" + FN
|
||||
parseIntervalSuccess(interval, FN)
|
||||
}
|
||||
}
|
||||
|
||||
interval.reset()
|
||||
result = append(result, interval)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseRandLR(lrvalue string) randLR {
|
||||
vlen := len(lrvalue)
|
||||
lastchar := lrvalue[vlen-1]
|
||||
|
||||
lr := strings.Split(lrvalue, "-")
|
||||
switch len(lr) {
|
||||
case 1:
|
||||
lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[0], lastchar)}
|
||||
return lr
|
||||
case 2:
|
||||
|
||||
lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[1], lastchar)}
|
||||
return lr
|
||||
default:
|
||||
panic("lr is error")
|
||||
}
|
||||
}
|
||||
|
||||
// intervalPriorityListISecond 获取优先链表比较的值
|
||||
func intervalPriorityListISecond(planlist *plist.PriorityList, count int) int {
|
||||
|
||||
if planlist.Size() > 0 {
|
||||
node := new(NodeCount)
|
||||
node.SetValue(count)
|
||||
|
||||
iwantNode := planlist.GetCompare(node)
|
||||
if iwantNode != nil {
|
||||
wantNode := iwantNode.(*NodeCount)
|
||||
lr := wantNode.randLR
|
||||
return randomdata.Number(lr.left, lr.right+1)
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func getInt(v string) int {
|
||||
vint, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return vint
|
||||
}
|
||||
|
||||
func parseTimeValue(v string, lastchar byte) int {
|
||||
|
||||
vlen := len(v)
|
||||
|
||||
switch lastchar {
|
||||
case 's':
|
||||
return getInt(v[:vlen-1])
|
||||
case 'm':
|
||||
return getInt(v[:vlen-1]) * 60
|
||||
case 'h':
|
||||
return getInt(v[:vlen-1]) * 3600
|
||||
case 'd':
|
||||
return getInt(v[:vlen-1]) * 3600 * 24
|
||||
default:
|
||||
return getInt(v)
|
||||
}
|
||||
}
|
10
trie_year.go
10
trie_year.go
|
@ -123,7 +123,7 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
|||
for j := 1; j <= 31; j++ {
|
||||
|
||||
if nmonth == i {
|
||||
if j < nday {
|
||||
if j < nday { // 获取当天的计划表
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
|||
if day != nil {
|
||||
|
||||
if day.IsClear {
|
||||
insertHour(cron, day)
|
||||
insertHour(cron, day) // 生成小时的计划表
|
||||
}
|
||||
|
||||
for k := 0; k <= 23; k++ {
|
||||
|
@ -144,7 +144,7 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
|||
}
|
||||
|
||||
hour := day.Hour[k]
|
||||
|
||||
// 遍历24小时 生成需要的计划表
|
||||
if hour != nil {
|
||||
|
||||
for n := 0; n <= 59; n++ {
|
||||
|
@ -159,7 +159,7 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
|||
|
||||
curTime := time.Date(ty.Year, time.Month(i), j, k, n, 0, 0, time.Local)
|
||||
result = append(result, curTime)
|
||||
count--
|
||||
count-- // 统计完当前需要的计划任务后就结束
|
||||
if count <= 0 {
|
||||
ty.clearHour()
|
||||
return result
|
||||
|
@ -168,8 +168,8 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
107
yearplan_base.go
Normal file
107
yearplan_base.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package crontab
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type timePointer struct {
|
||||
left, right int
|
||||
leftlimit, rightlimit int
|
||||
per int
|
||||
isAll bool
|
||||
}
|
||||
|
||||
func (tp *timePointer) String() string {
|
||||
return fmt.Sprintf("left: %d, right: %d, leftlimit: %d, rightlimit: %d, per: %d", tp.left, tp.right, tp.leftlimit, tp.rightlimit, tp.per)
|
||||
}
|
||||
|
||||
func createTimePointer(min string, llimit, rlimit int, fixedLeftRight bool) []timePointer {
|
||||
|
||||
var result []timePointer
|
||||
|
||||
exelist := strings.Split(min, ",")
|
||||
for _, exe := range exelist {
|
||||
tp := timePointer{}
|
||||
|
||||
takeper := strings.Split(exe, "/") // per
|
||||
var rangevalue, per string
|
||||
if len(takeper) == 1 {
|
||||
rangevalue = exe
|
||||
per = "1"
|
||||
} else {
|
||||
rangevalue = takeper[0]
|
||||
per = takeper[1]
|
||||
}
|
||||
// takeRange
|
||||
be := strings.Split(rangevalue, "-")
|
||||
var left, rigth string
|
||||
switch len(be) {
|
||||
case 1:
|
||||
left = be[0]
|
||||
rigth = be[0]
|
||||
case 2:
|
||||
left = be[0]
|
||||
rigth = be[1]
|
||||
default:
|
||||
panic(errors.New("range value is > 2"))
|
||||
}
|
||||
|
||||
if left == "*" {
|
||||
tp.left = llimit
|
||||
} else {
|
||||
ileft, err := strconv.Atoi(strings.Replace(left, "^", "-", -1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tp.left = ileft
|
||||
}
|
||||
|
||||
if rigth == "*" {
|
||||
tp.right = rlimit
|
||||
} else {
|
||||
iright, err := strconv.Atoi(strings.Replace(rigth, "^", "-", -1))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tp.right = iright
|
||||
}
|
||||
|
||||
iper, err := strconv.Atoi(per)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tp.per = iper
|
||||
tp.leftlimit = llimit
|
||||
tp.rightlimit = rlimit
|
||||
|
||||
// 修正左值
|
||||
leftfixed := tp.left
|
||||
if leftfixed < 0 {
|
||||
leftfixed += tp.rightlimit + 1
|
||||
if fixedLeftRight {
|
||||
tp.left = leftfixed
|
||||
}
|
||||
}
|
||||
|
||||
rightfixed := tp.right
|
||||
if rightfixed < 0 {
|
||||
rightfixed += tp.rightlimit + 1
|
||||
if fixedLeftRight {
|
||||
tp.right = rightfixed
|
||||
}
|
||||
}
|
||||
|
||||
// 全部符合 当左等于左 且 右等于右最大 并且 per == 1
|
||||
if leftfixed == tp.leftlimit && rightfixed == tp.rightlimit && tp.per == 1 {
|
||||
tp.isAll = true
|
||||
}
|
||||
|
||||
result = append(result, tp)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
Loading…
Reference in New Issue
Block a user