Compare commits
No commits in common. "master" and "v0.0.1" have entirely different histories.
|
@ -1 +0,0 @@
|
||||||
package crontab
|
|
413
crontab.go
413
crontab.go
|
@ -4,49 +4,46 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
clinked "474420502.top/eson/structure/circular_linked"
|
||||||
|
|
||||||
"474420502.top/eson/structure/circular_linked"
|
|
||||||
"github.com/Pallinder/go-randomdata"
|
"github.com/Pallinder/go-randomdata"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusType 设置状态的类型
|
type randLR struct {
|
||||||
type StatusType int
|
left, right int
|
||||||
|
|
||||||
const (
|
|
||||||
_ StatusType = iota
|
|
||||||
// SExecuteOK 设置这个次成功或者失败的状态
|
|
||||||
SExecuteOK
|
|
||||||
// SExecuteSleep 设置下次Sleep时间, 只影响一次
|
|
||||||
SExecuteSleep
|
|
||||||
// SExecuteCrontab 设置改写Crontab, 覆盖以前的Crontab
|
|
||||||
SExecuteCrontab
|
|
||||||
)
|
|
||||||
|
|
||||||
// Force 强制下次执行
|
|
||||||
type Force struct {
|
|
||||||
sleep time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextTime 下次执行时间
|
type hInterval struct {
|
||||||
func (force *Force) NextTime() time.Time {
|
PlanFail []randLR
|
||||||
return time.Now().Add(force.sleep)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crontab 的string解析
|
// Crontab 的string解析
|
||||||
type Crontab struct {
|
type Crontab struct {
|
||||||
crontab string
|
|
||||||
|
|
||||||
uid uuid.UUID
|
|
||||||
force *Force
|
|
||||||
|
|
||||||
min []timePointer
|
min []timePointer
|
||||||
hour []timePointer
|
hour []timePointer
|
||||||
day []timePointer
|
day []timePointer
|
||||||
|
@ -60,74 +57,33 @@ type Crontab struct {
|
||||||
|
|
||||||
interval *clinked.CircularLinked
|
interval *clinked.CircularLinked
|
||||||
lastStatus bool
|
lastStatus bool
|
||||||
isCalculated bool
|
|
||||||
|
|
||||||
trueCount int
|
|
||||||
failCount int
|
|
||||||
nextTime time.Time
|
nextTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCrontab create 一个crontab
|
// NewCrontab create 一个crontab
|
||||||
func NewCrontab(crontab string) *Crontab {
|
func NewCrontab(crontab string) *Crontab {
|
||||||
cron := &Crontab{}
|
cron := &Crontab{}
|
||||||
cron.crontab = strings.TrimSpace(crontab)
|
cron.FromString(crontab)
|
||||||
cron.FromString(cron.crontab)
|
|
||||||
return cron
|
return cron
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML 添加序列化接口 Marshal没实现, 需要的时候可以自己添加
|
// SetStatus 设置状态 接口定义
|
||||||
func (cron *Crontab) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (cron *Crontab) SetStatus(status interface{}) {
|
||||||
var buf string
|
|
||||||
err := unmarshal(&buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cron.FromString(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatus 设置上次状态 true false
|
|
||||||
func (cron *Crontab) SetStatus(statusType StatusType, statusValue ...interface{}) {
|
|
||||||
|
|
||||||
switch statusType {
|
|
||||||
case SExecuteOK:
|
|
||||||
if cron.interval != nil {
|
if cron.interval != nil {
|
||||||
cron.lastStatus = statusValue[0].(bool)
|
cron.lastStatus = status.(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 设置状态 接口定义
|
||||||
|
func (cron *Crontab) GetStatus() (status interface{}) {
|
||||||
|
if cron.interval != nil {
|
||||||
|
return cron.lastStatus
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// // GetStatus 获取上次状态 true false
|
// TimeUp 是否时间快到
|
||||||
// func (cron *Crontab) GetStatus() (status bool) {
|
|
||||||
// return cron.lastStatus
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TimeUp 是否时间快到, 时间间隔调用完TimeUp后必须调用NextTime, 为了精准控制时间
|
|
||||||
func (cron *Crontab) TimeUp() bool {
|
func (cron *Crontab) TimeUp() bool {
|
||||||
|
|
||||||
if cron.interval != nil {
|
if cron.interval != nil {
|
||||||
|
@ -138,27 +94,16 @@ func (cron *Crontab) TimeUp() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextTime 返回下次任务的时间
|
// 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 {
|
if cron.interval != nil {
|
||||||
if !cron.isCalculated {
|
return &cron.nextTime
|
||||||
now := time.Now()
|
|
||||||
cron.intervalCalculateNextTime(now)
|
|
||||||
}
|
|
||||||
return cron.nextTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cron.WillPlans) > 0 {
|
if len(cron.WillPlans) > 0 {
|
||||||
return cron.WillPlans[0]
|
return &cron.WillPlans[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Now().Add(time.Second * 2)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cron *Crontab) String() string {
|
func (cron *Crontab) String() string {
|
||||||
|
@ -167,36 +112,15 @@ func (cron *Crontab) String() string {
|
||||||
|
|
||||||
// FromString 解析crontab 的 表达式
|
// FromString 解析crontab 的 表达式
|
||||||
func (cron *Crontab) FromString(crontab string) error {
|
func (cron *Crontab) FromString(crontab string) error {
|
||||||
crontab = cron.crontab
|
crontab = strings.TrimSpace(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)
|
matches := regexp.MustCompile("[^ ]+").FindAllString(crontab, -1)
|
||||||
mlen := len(matches)
|
mlen := len(matches)
|
||||||
switch mlen {
|
switch mlen {
|
||||||
case 1:
|
case 1:
|
||||||
// "f1-2|5-10x5,f1|10m,10-15,f1"
|
// "f1-2|5-10x5,f1|10m,10-15,f1"
|
||||||
|
|
||||||
cron.lastStatus = true
|
|
||||||
cron.isCalculated = true
|
|
||||||
cron.nextTime = time.Now()
|
cron.nextTime = time.Now()
|
||||||
|
cron.lastStatus = true
|
||||||
cron.interval = clinked.NewCircularLinked()
|
cron.interval = clinked.NewCircularLinked()
|
||||||
var intervalList []interface{}
|
var intervalList []interface{}
|
||||||
intervalList = parseIntervalString(matches[0])
|
intervalList = parseIntervalString(matches[0])
|
||||||
|
@ -210,7 +134,6 @@ func (cron *Crontab) FromString(crontab string) error {
|
||||||
cron.week = createTimePointer(matches[4], 0, 6, true)
|
cron.week = createTimePointer(matches[4], 0, 6, true)
|
||||||
|
|
||||||
cron.createYearPlan()
|
cron.createYearPlan()
|
||||||
cron.TimeUp()
|
|
||||||
default:
|
default:
|
||||||
return errors.New("mathches len != want, check crontab string")
|
return errors.New("mathches len != want, check crontab string")
|
||||||
}
|
}
|
||||||
|
@ -230,7 +153,7 @@ func (cron *Crontab) linuxTimeUp() bool {
|
||||||
createlen := 500
|
createlen := 500
|
||||||
|
|
||||||
plen := len(cron.WillPlans)
|
plen := len(cron.WillPlans)
|
||||||
if plen <= createlen { // 如果当前生成的计划表少于 限制的500. 就生成新的计划表
|
if plen <= createlen {
|
||||||
var lastplan time.Time
|
var lastplan time.Time
|
||||||
if plen == 0 {
|
if plen == 0 {
|
||||||
lastplan = now
|
lastplan = now
|
||||||
|
@ -248,13 +171,9 @@ func (cron *Crontab) linuxTimeUp() bool {
|
||||||
if len(cron.WillPlans) > 0 {
|
if len(cron.WillPlans) > 0 {
|
||||||
istimeup := false
|
istimeup := false
|
||||||
for i := 0; i < maxlen; i++ {
|
for i := 0; i < maxlen; i++ {
|
||||||
|
|
||||||
// 统计过了多少计划任务时间表 i - 1
|
|
||||||
if now.Unix() >= cron.WillPlans[i].Unix() {
|
if now.Unix() >= cron.WillPlans[i].Unix() {
|
||||||
istimeup = true
|
istimeup = true
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// 清除过时的计划任务时间表
|
|
||||||
if istimeup {
|
if istimeup {
|
||||||
if i-1 > 0 {
|
if i-1 > 0 {
|
||||||
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...)
|
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...)
|
||||||
|
@ -269,7 +188,7 @@ func (cron *Crontab) linuxTimeUp() bool {
|
||||||
return istimeup
|
return istimeup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果所有计划表都不符合, 全部放到忽略的计划表上, 这个表方便打印查看, 因为程序执行超时过了多少计划任务
|
|
||||||
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...)
|
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...)
|
||||||
cron.WillPlans = nil
|
cron.WillPlans = nil
|
||||||
return istimeup
|
return istimeup
|
||||||
|
@ -279,52 +198,21 @@ func (cron *Crontab) linuxTimeUp() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntervalCalculateNextTime 计算时间间隔的下次时间
|
func (cron *Crontab) intervalTimeUp() bool {
|
||||||
func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
|
|
||||||
iv := cron.interval.Cursor().GetValue().(*hInterval)
|
now := time.Now()
|
||||||
|
if now.Unix() >= cron.nextTime.Unix() {
|
||||||
|
|
||||||
|
iv := cron.interval.Cursor().GetValue().(hInterval)
|
||||||
isecond := 0
|
isecond := 0
|
||||||
|
if cron.lastStatus == false && len(iv.PlanFail) > 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))
|
idx := randomdata.Number(len(iv.PlanFail))
|
||||||
lr := iv.PlanFail[idx]
|
lr := iv.PlanFail[idx]
|
||||||
isecond = randomdata.Number(lr.left, lr.right+1)
|
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||||
} else {
|
} else {
|
||||||
isecond = 0
|
idx := randomdata.Number(len(iv.PlanNormal))
|
||||||
}
|
lr := iv.PlanNormal[idx]
|
||||||
}
|
isecond = randomdata.Number(lr.left, lr.right+1)
|
||||||
|
|
||||||
fmt.Println(time.Now().Format("01-02 15:04:05"), cron.uid.String(), "fail:", cron.failCount, " wait:", isecond)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iv.Count--
|
iv.Count--
|
||||||
|
@ -333,19 +221,192 @@ func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
|
||||||
cron.interval.MoveNext()
|
cron.interval.MoveNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
cron.isCalculated = true
|
|
||||||
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
|
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
|
||||||
}
|
|
||||||
|
|
||||||
func (cron *Crontab) intervalTimeUp() bool {
|
|
||||||
|
|
||||||
if cron.isCalculated { // 需要调用nexttime()才能正常正常计算下次的时间
|
|
||||||
now := time.Now()
|
|
||||||
if now.Unix() >= cron.nextTime.Unix() {
|
|
||||||
cron.isCalculated = false
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
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,12 +2,9 @@ package crontab
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// type LRValue struct {
|
// type LRValue struct {
|
||||||
|
@ -29,8 +26,8 @@ func TestParseCrontab(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cron.createYearPlan()
|
cron.createYearPlan()
|
||||||
if cron.TimeUp() {
|
if !cron.TimeUp() {
|
||||||
t.Error("timeup error", cron.WillPlans[0:2])
|
t.Error("timeup error")
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintMemUsage()
|
PrintMemUsage()
|
||||||
|
@ -39,13 +36,13 @@ func TestParseCrontab(t *testing.T) {
|
||||||
|
|
||||||
func TestParseInterval(t *testing.T) {
|
func TestParseInterval(t *testing.T) {
|
||||||
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f22?15-12"
|
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1"
|
||||||
crontab := "2s"
|
crontab := "2"
|
||||||
cron := NewCrontab(crontab)
|
cron := NewCrontab(crontab)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for i := 0; i <= 3; i++ {
|
for i := 0; i <= 6; i++ {
|
||||||
log.Println(cron.NextTime())
|
|
||||||
if cron.TimeUp() {
|
if cron.TimeUp() {
|
||||||
|
|
||||||
sec := time.Since(now).Seconds()
|
sec := time.Since(now).Seconds()
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
if 2.0 <= sec && sec <= 2.1 {
|
if 2.0 <= sec && sec <= 2.1 {
|
||||||
|
@ -61,85 +58,13 @@ 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) {
|
func TestParseIntervalPlus(t *testing.T) {
|
||||||
// crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
// crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||||
// crondata := NewCrontab("*22 * * * *")
|
// crondata := NewCrontab("*22 * * * *")
|
||||||
|
|
||||||
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
//crontab := "0-5/2,7-30/3,30,35,40-^1 * * * *" //(秒) 分 时 号(每月的多少号, 要注意月可可能性) 星期几(每个星期的) /每 ,列表 -范围
|
||||||
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1" ^代表往后算 ^5 2月份就代表 28-4=24 f代表失败后1-2秒设置 由SetStatus控制 t 相反默认不写前缀为t
|
//crontab := "f1-2|1-3|5-8x5,f1|10m,10-15,f1"
|
||||||
|
|
||||||
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() {
|
func PrintMemUsage() {
|
||||||
|
|
205
interval_base.go
205
interval_base.go
|
@ -1,205 +0,0 @@
|
||||||
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++ {
|
for j := 1; j <= 31; j++ {
|
||||||
|
|
||||||
if nmonth == i {
|
if nmonth == i {
|
||||||
if j < nday { // 获取当天的计划表
|
if j < nday {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
||||||
if day != nil {
|
if day != nil {
|
||||||
|
|
||||||
if day.IsClear {
|
if day.IsClear {
|
||||||
insertHour(cron, day) // 生成小时的计划表
|
insertHour(cron, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := 0; k <= 23; k++ {
|
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]
|
hour := day.Hour[k]
|
||||||
// 遍历24小时 生成需要的计划表
|
|
||||||
if hour != nil {
|
if hour != nil {
|
||||||
|
|
||||||
for n := 0; n <= 59; n++ {
|
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)
|
curTime := time.Date(ty.Year, time.Month(i), j, k, n, 0, 0, time.Local)
|
||||||
result = append(result, curTime)
|
result = append(result, curTime)
|
||||||
count-- // 统计完当前需要的计划任务后就结束
|
count--
|
||||||
if count <= 0 {
|
if count <= 0 {
|
||||||
ty.clearHour()
|
ty.clearHour()
|
||||||
return result
|
return result
|
||||||
|
@ -168,8 +168,8 @@ func (ty *trieYear) GetPlanTime(cron *Crontab, aftertime time.Time, count uint)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
107
yearplan_base.go
107
yearplan_base.go
|
@ -1,107 +0,0 @@
|
||||||
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