2019-02-02 16:18:25 +08:00
|
|
|
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
2018-12-30 11:08:07 +08:00
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
|
|
// If a copy of the MIT was not distributed with this file,
|
2019-02-02 16:18:25 +08:00
|
|
|
// You can obtain one at https://github.com/gogf/gf.
|
2018-12-30 11:08:07 +08:00
|
|
|
|
|
|
|
package gcron
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-02-02 16:18:25 +08:00
|
|
|
"github.com/gogf/gf/g/text/gregex"
|
2018-12-30 11:08:07 +08:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 运行时间管理对象
|
|
|
|
type cronSchedule struct {
|
|
|
|
create int64 // 创建时间戳(秒)
|
|
|
|
every int64 // 运行时间间隔(秒)
|
|
|
|
pattern string // 原始注册字符串
|
|
|
|
second map[int]struct{}
|
|
|
|
minute map[int]struct{}
|
|
|
|
hour map[int]struct{}
|
|
|
|
day map[int]struct{}
|
|
|
|
week map[int]struct{}
|
|
|
|
month map[int]struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
gREGEX_FOR_CRON = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)$`
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// 预定义的定时格式
|
|
|
|
predefinedPatternMap = map[string]string{
|
|
|
|
"@yearly" : "0 0 0 1 1 *",
|
|
|
|
"@annually" : "0 0 0 1 1 *",
|
|
|
|
"@monthly" : "0 0 0 1 * *",
|
|
|
|
"@weekly" : "0 0 0 * * 0",
|
|
|
|
"@daily" : "0 0 0 * * *",
|
|
|
|
"@midnight" : "0 0 0 * * *",
|
|
|
|
"@hourly" : "0 0 * * * *",
|
|
|
|
}
|
|
|
|
// 月份与数字对应表
|
|
|
|
monthMap = 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,
|
|
|
|
}
|
|
|
|
// 星期与数字对应表
|
|
|
|
weekMap = map[string]int {
|
|
|
|
"sun": 0,
|
|
|
|
"mon": 1,
|
|
|
|
"tue": 2,
|
|
|
|
"wed": 3,
|
|
|
|
"thu": 4,
|
|
|
|
"fri": 5,
|
|
|
|
"sat": 6,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// 解析定时格式为cronSchedule对象
|
|
|
|
func newSchedule(pattern string) (*cronSchedule, error) {
|
|
|
|
// 处理预定义的定时格式
|
|
|
|
if match, _ := gregex.MatchString(`(@\w+)\s*(\w*)\s*`, pattern); len(match) > 0 {
|
|
|
|
key := strings.ToLower(match[1])
|
|
|
|
if v, ok := predefinedPatternMap[key]; ok {
|
|
|
|
pattern = v
|
|
|
|
} else if strings.Compare(key, "@every") == 0 {
|
|
|
|
if d, err := time.ParseDuration(match[2]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
return &cronSchedule {
|
|
|
|
create : time.Now().Unix(),
|
|
|
|
every : int64(d.Seconds()),
|
|
|
|
pattern : pattern,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 处理通用的定时格式定义
|
|
|
|
if match, _ := gregex.MatchString(gREGEX_FOR_CRON, pattern); len(match) == 7 {
|
|
|
|
schedule := &cronSchedule {
|
|
|
|
create : time.Now().Unix(),
|
|
|
|
every : 0,
|
|
|
|
pattern : pattern,
|
|
|
|
}
|
|
|
|
// 秒
|
|
|
|
if m, err := parseItem(match[1], 0, 59, false); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
schedule.second = m
|
|
|
|
}
|
|
|
|
// 分
|
|
|
|
if m, err := parseItem(match[2], 0, 59, false); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
schedule.minute = m
|
|
|
|
}
|
|
|
|
// 时
|
|
|
|
if m, err := parseItem(match[3], 0, 23, false); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
schedule.hour = m
|
|
|
|
}
|
|
|
|
// 天
|
2019-02-20 16:24:05 +08:00
|
|
|
if m, err := parseItem(match[4], 1, 31, true); err != nil {
|
2018-12-30 11:08:07 +08:00
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
schedule.day = m
|
|
|
|
}
|
2019-02-20 16:24:05 +08:00
|
|
|
// 月
|
|
|
|
if m, err := parseItem(match[5], 1, 12, false); err != nil {
|
2018-12-30 11:08:07 +08:00
|
|
|
return nil, err
|
|
|
|
} else {
|
2019-02-20 16:24:05 +08:00
|
|
|
schedule.month = m
|
2018-12-30 11:08:07 +08:00
|
|
|
}
|
2019-02-20 16:24:05 +08:00
|
|
|
// 周
|
|
|
|
if m, err := parseItem(match[6], 0, 6, true); err != nil {
|
2018-12-30 11:08:07 +08:00
|
|
|
return nil, err
|
|
|
|
} else {
|
2019-02-20 16:24:05 +08:00
|
|
|
schedule.week = m
|
2018-12-30 11:08:07 +08:00
|
|
|
}
|
|
|
|
return schedule, nil
|
|
|
|
} else {
|
|
|
|
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 解析定时格式中的每一项定时配置
|
|
|
|
func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
|
|
|
|
m := make(map[int]struct{}, max - min + 1)
|
|
|
|
if item == "*" || (allowQuestionMark && item == "?") {
|
|
|
|
for i := min; i <= max; i++ {
|
|
|
|
m[i] = struct{}{}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for _, item := range strings.Split(item, ",") {
|
|
|
|
interval := 1
|
|
|
|
intervalArray := strings.Split(item, "/")
|
|
|
|
if len(intervalArray) == 2 {
|
|
|
|
if i, err := strconv.Atoi(intervalArray[1]); err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
|
|
|
|
} else {
|
|
|
|
interval = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rangeMin := min
|
|
|
|
rangeMax := max
|
|
|
|
rangeArray := strings.Split(intervalArray[0], "-")
|
|
|
|
valueType := byte(0)
|
|
|
|
switch max {
|
|
|
|
case 6: valueType = 'w'
|
|
|
|
case 11: valueType = 'm'
|
|
|
|
}
|
|
|
|
// 例如: */5
|
|
|
|
if rangeArray[0] != "*" {
|
|
|
|
if i, err := parseItemValue(rangeArray[0], valueType); err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
|
|
|
|
} else {
|
|
|
|
rangeMin = i
|
|
|
|
rangeMax = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(rangeArray) == 2 {
|
|
|
|
if i, err := parseItemValue(rangeArray[1], valueType); err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf(`invalid pattern item: "%s"`, item))
|
|
|
|
} else {
|
|
|
|
rangeMax = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := rangeMin; i <= rangeMax; i += interval {
|
|
|
|
m[i] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 将配置项值转换为数字
|
|
|
|
func parseItemValue(value string, valueType byte) (int, error) {
|
|
|
|
if gregex.IsMatchString(`^\d+$`, value) {
|
|
|
|
// 纯数字
|
|
|
|
if i, err := strconv.Atoi(value); err == nil {
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// 英文字母
|
|
|
|
switch valueType {
|
|
|
|
case 'm':
|
|
|
|
if i, ok := monthMap[strings.ToLower(value)]; ok {
|
|
|
|
return int(i), nil
|
|
|
|
}
|
2019-02-20 16:24:05 +08:00
|
|
|
case 'w':
|
|
|
|
if i, ok := weekMap[strings.ToLower(value)]; ok {
|
|
|
|
return int(i), nil
|
|
|
|
}
|
2018-12-30 11:08:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 判断给定的时间是否满足schedule
|
|
|
|
func (s *cronSchedule) meet(t time.Time) bool {
|
|
|
|
if s.every != 0 {
|
|
|
|
diff := t.Unix() - s.create
|
|
|
|
if diff > 0 {
|
|
|
|
return diff%s.every == 0
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
} else {
|
|
|
|
if _, ok := s.second[t.Second()]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, ok := s.minute[t.Minute()]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, ok := s.hour[t.Hour()]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if _, ok := s.day[t.Day()]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
2019-02-20 16:24:05 +08:00
|
|
|
if _, ok := s.month[int(t.Month())]; !ok {
|
2018-12-30 11:08:07 +08:00
|
|
|
return false
|
|
|
|
}
|
2019-02-20 16:24:05 +08:00
|
|
|
if _, ok := s.week[int(t.Weekday())]; !ok {
|
2018-12-30 11:08:07 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|