golang cron使用
2019-10-29 19:10:3 Author: cloudsjhan.github.io(查看原文) 阅读量:0 收藏

cron表达式是一个好东西,这个东西不仅Java的quartZ能用到,Go语言中也可以用到。我没有用过Linux的cron,但网上说Linux也是可以用crontab -e 命令来配置定时任务。Go语言和Java中都是可以精确到秒的,但是Linux中不行。

表示增长间隔,如第1个字段(minutes) 值是 3-59/15,表示每小时的第3分钟开始执行一次,之后每隔 15 分钟执行一次(即 3、18、33、48 这些时间点执行),这里也可以表示为:3/15

1
`// SpecSchedule specifies a duty cycle (to the second granularity), based on a``// traditional crontab specification. It is computed initially and stored as bit sets.``type SpecSchedule struct {`` ``// 表达式中锁表明的,秒,分,时,日,月,周,每个都是uint64`` ``// Dom:Day of Month,Dow:Day of week`` ``Second, Minute, Hour, Dom, Month, Dow uint64``}` `// bounds provides a range of acceptable values (plus a map of name to value).``// 定义了表达式的结构体``type bounds struct {`` ``min, max uint`` ``names map[string]uint``}` `// The bounds for each field.``// 这样就能看出各个表达式的范围``var (``  ``seconds = bounds{0, 59, nil}``  ``minutes = bounds{0, 59, nil}``  ``hours = bounds{0, 23, nil}``  ``dom  = bounds{1, 31, nil}``  ``months = bounds{1, 12, map[string]uint{``    ``"jan": 1,``    ``"feb": 2,``    ``"mar": 3,``    ``"apr": 4,``    ``"may": 5,``    ``"jun": 6,``    ``"jul": 7,``    ``"aug": 8,``    ``"sep": 9,``    ``"oct": 10,``    ``"nov": 11,``    ``"dec": 12,``  ``}}``  ``dow = bounds{0, 6, map[string]uint{``    ``"sun": 0,``    ``"mon": 1,``    ``"tue": 2,``    ``"wed": 3,``    ``"thu": 4,``    ``"fri": 5,``    ``"sat": 6,``  ``}}``)` `const (``  ``// Set the top bit if a star was included in the expression.``  ``starBit = 1 << 63``)`

看了上面的东西肯定有人疑惑为什么秒分时这些都是定义了unit64,以及定义了一个常量starBit = 1 << 63这种写法,这是逻辑运算符。表示二进制1向左移动63位。原因如下:

cron表达式是用来表示一系列时间的,而时间是无法逃脱自己的区间的 , 分,秒 0 - 59 , 时 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 这些本质上都是一个点集合,或者说是一个整数区间。 那么对于任意的整数区间 , 可以描述cron的如下部分规则。

至此, robfig/cron为什么不支持 L | W的原因已经明了了。去除这两条规则后, 其余的规则其实完全可以使用点的穷举来通用表示。 考虑到最大的区间也不过是60个点,那么使用一个uint64的整数的每一位来表示一个点便很合适了。所以定义unit64不为过

1
`package cron` `import (`` ``"fmt"`` ``"math"`` ``"strconv"`` ``"strings"`` ``"time"``)` `// Configuration options for creating a parser. Most options specify which``// fields should be included, while others enable features. If a field is not``// included the parser will assume a default value. These options do not change``// the order fields are parse in.``type ParseOption int` `const (`` ``Second  ParseOption = 1 << iota // Seconds field, default 0`` ``Minute        // Minutes field, default 0`` ``Hour        // Hours field, default 0`` ``Dom         // Day of month field, default *`` ``Month        // Month field, default *`` ``Dow         // Day of week field, default *`` ``DowOptional       // Optional day of week field, default *`` ``Descriptor       // Allow descriptors such as @monthly, @weekly, etc.``)` `var places = []ParseOption{`` ``Second,`` ``Minute,`` ``Hour,`` ``Dom,`` ``Month,`` ``Dow,``}` `var defaults = []string{`` ``"0",`` ``"0",`` ``"0",`` ``"*",`` ``"*",`` ``"*",``}` `// A custom Parser that can be configured.``type Parser struct {`` ``options ParseOption`` ``optionals int``}` `// Creates a custom Parser with custom options.``//``// // Standard parser without descriptors``// specParser := NewParser(Minute | Hour | Dom | Month | Dow)``// sched, err := specParser.Parse("0 0 15 */3 *")``//``// // Same as above, just excludes time fields``// subsParser := NewParser(Dom | Month | Dow)``// sched, err := specParser.Parse("15 */3 *")``//``// // Same as above, just makes Dow optional``// subsParser := NewParser(Dom | Month | DowOptional)``// sched, err := specParser.Parse("15 */3")``//``func NewParser(options ParseOption) Parser {`` ``optionals := 0`` ``if options&DowOptional > 0 {``  ``options |= Dow``  ``optionals++`` ``}`` ``return Parser{options, optionals}``}` `// Parse returns a new crontab schedule representing the given spec.``// It returns a descriptive error if the spec is not valid.``// It accepts crontab specs and features configured by NewParser.``// 将字符串解析成为SpecSchedule 。 SpecSchedule符合Schedule接口` `func (p Parser) Parse(spec string) (Schedule, error) {``  // 直接处理特殊的特殊的字符串`` ``if spec[0] == '@' && p.options&Descriptor > 0 {``  ``return parseDescriptor(spec)`` ``}` ` ``// Figure out how many fields we need`` ``max := 0`` ``for _, place := range places {``  ``if p.options&place > 0 {``   ``max++``  ``}`` ``}`` ``min := max - p.optionals` ` ``// cron利用空白拆解出独立的items。`` ``fields := strings.Fields(spec)` ` ``// 验证表达式取值范围`` ``if count := len(fields); count < min || count > max {``  ``if min == max {``   ``return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)``  ``}``  ``return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)`` ``}` ` ``// Fill in missing fields`` ``fields = expandFields(fields, p.options)` ` ``var err error`` ``field := func(field string, r bounds) uint64 {``  ``if err != nil {``   ``return 0``  ``}``  ``var bits uint64``  ``bits, err = getField(field, r)``  ``return bits`` ``}` ` ``var (``  ``second  = field(fields[0], seconds)``  ``minute  = field(fields[1], minutes)``  ``hour  = field(fields[2], hours)``  ``dayofmonth = field(fields[3], dom)``  ``month  = field(fields[4], months)``  ``dayofweek = field(fields[5], dow)`` ``)`` ``if err != nil {``  ``return nil, err`` ``}`` ``// 返回所需要的SpecSchedule`` ``return &SpecSchedule{``  ``Second: second,``  ``Minute: minute,``  ``Hour: hour,``  ``Dom: dayofmonth,``  ``Month: month,``  ``Dow: dayofweek,`` ``}, nil``}` `func expandFields(fields []string, options ParseOption) []string {`` ``n := 0`` ``count := len(fields)`` ``expFields := make([]string, len(places))`` ``copy(expFields, defaults)`` ``for i, place := range places {``  ``if options&place > 0 {``   ``expFields[i] = fields[n]``   ``n++``  ``}``  ``if n == count {``   ``break``  ``}`` ``}`` ``return expFields``}` `var standardParser = NewParser(`` ``Minute | Hour | Dom | Month | Dow | Descriptor,``)` `// ParseStandard returns a new crontab schedule representing the given standardSpec``// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always``// pass 5 entries representing: minute, hour, day of month, month and day of week,``// in that order. It returns a descriptive error if the spec is not valid.``//``// It accepts``// - Standard crontab specs, e.g. "* * * * ?"``// - Descriptors, e.g. "@midnight", "@every 1h30m"``// 这里表示不仅可以使用cron表达式,也可以使用@midnight @every等方法` `func ParseStandard(standardSpec string) (Schedule, error) {`` ``return standardParser.Parse(standardSpec)``}` `var defaultParser = NewParser(`` ``Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,``)` `// Parse returns a new crontab schedule representing the given spec.``// It returns a descriptive error if the spec is not valid.``//``// It accepts``// - Full crontab specs, e.g. "* * * * * ?"``// - Descriptors, e.g. "@midnight", "@every 1h30m"``func Parse(spec string) (Schedule, error) {`` ``return defaultParser.Parse(spec)``}` `// getField returns an Int with the bits set representing all of the times that``// the field represents or error parsing field value. A "field" is a comma-separated``// list of "ranges".``func getField(field string, r bounds) (uint64, error) {`` ``var bits uint64`` ``ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })`` ``for _, expr := range ranges {``  ``bit, err := getRange(expr, r)``  ``if err != nil {``   ``return bits, err``  ``}``  ``bits |= bit`` ``}`` ``return bits, nil``}` `// getRange returns the bits indicated by the given expression:``// number | number "-" number [ "/" number ]``// or error parsing range.``func getRange(expr string, r bounds) (uint64, error) {`` ``var (``  ``start, end, step uint``  ``rangeAndStep  = strings.Split(expr, "/")``  ``lowAndHigh  = strings.Split(rangeAndStep[0], "-")``  ``singleDigit  = len(lowAndHigh) == 1``  ``err    error`` ``)` ` ``var extra uint64`` ``if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {``  ``start = r.min``  ``end = r.max``  ``extra = starBit`` ``} else {``  ``start, err = parseIntOrName(lowAndHigh[0], r.names)``  ``if err != nil {``   ``return 0, err``  ``}``  ``switch len(lowAndHigh) {``  ``case 1:``   ``end = start``  ``case 2:``   ``end, err = parseIntOrName(lowAndHigh[1], r.names)``   ``if err != nil {``    ``return 0, err``   ``}``  ``default:``   ``return 0, fmt.Errorf("Too many hyphens: %s", expr)``  ``}`` ``}` ` ``switch len(rangeAndStep) {`` ``case 1:``  ``step = 1`` ``case 2:``  ``step, err = mustParseInt(rangeAndStep[1])``  ``if err != nil {``   ``return 0, err``  ``}` `  ``// Special handling: "N/step" means "N-max/step".``  ``if singleDigit {``   ``end = r.max``  ``}`` ``default:``  ``return 0, fmt.Errorf("Too many slashes: %s", expr)`` ``}` ` ``if start < r.min {``  ``return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)`` ``}`` ``if end > r.max {``  ``return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)`` ``}`` ``if start > end {``  ``return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)`` ``}`` ``if step == 0 {``  ``return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)`` ``}` ` ``return getBits(start, end, step) | extra, nil``}` `// parseIntOrName returns the (possibly-named) integer contained in expr.``func parseIntOrName(expr string, names map[string]uint) (uint, error) {`` ``if names != nil {``  ``if namedInt, ok := names[strings.ToLower(expr)]; ok {``   ``return namedInt, nil``  ``}`` ``}`` ``return mustParseInt(expr)``}` `// mustParseInt parses the given expression as an int or returns an error.``func mustParseInt(expr string) (uint, error) {`` ``num, err := strconv.Atoi(expr)`` ``if err != nil {``  ``return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)`` ``}`` ``if num < 0 {``  ``return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)`` ``}` ` ``return uint(num), nil``}` `// getBits sets all bits in the range [min, max], modulo the given step size.``func getBits(min, max, step uint) uint64 {`` ``var bits uint64` ` ``// If step is 1, use shifts.`` ``if step == 1 {``  ``return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)`` ``}` ` ``// Else, use a simple loop.`` ``for i := min; i <= max; i += step {``  ``bits |= 1 << i`` ``}`` ``return bits``}` `// all returns all bits within the given bounds. (plus the star bit)``func all(r bounds) uint64 {`` ``return getBits(r.min, r.max, 1) | starBit``}` `// parseDescriptor returns a predefined schedule for the expression, or error if none matches.``func parseDescriptor(descriptor string) (Schedule, error) {`` ``switch descriptor {`` ``case "@yearly", "@annually":``  ``return &SpecSchedule{``   ``Second: 1 << seconds.min,``   ``Minute: 1 << minutes.min,``   ``Hour: 1 << hours.min,``   ``Dom: 1 << dom.min,``   ``Month: 1 << months.min,``   ``Dow: all(dow),``  ``}, nil` ` ``case "@monthly":``  ``return &SpecSchedule{``   ``Second: 1 << seconds.min,``   ``Minute: 1 << minutes.min,``   ``Hour: 1 << hours.min,``   ``Dom: 1 << dom.min,``   ``Month: all(months),``   ``Dow: all(dow),``  ``}, nil` ` ``case "@weekly":``  ``return &SpecSchedule{``   ``Second: 1 << seconds.min,``   ``Minute: 1 << minutes.min,``   ``Hour: 1 << hours.min,``   ``Dom: all(dom),``   ``Month: all(months),``   ``Dow: 1 << dow.min,``  ``}, nil` ` ``case "@daily", "@midnight":``  ``return &SpecSchedule{``   ``Second: 1 << seconds.min,``   ``Minute: 1 << minutes.min,``   ``Hour: 1 << hours.min,``   ``Dom: all(dom),``   ``Month: all(months),``   ``Dow: all(dow),``  ``}, nil` ` ``case "@hourly":``  ``return &SpecSchedule{``   ``Second: 1 << seconds.min,``   ``Minute: 1 << minutes.min,``   ``Hour: all(hours),``   ``Dom: all(dom),``   ``Month: all(months),``   ``Dow: all(dow),``  ``}, nil`` ``}` ` ``const every = "@every "`` ``if strings.HasPrefix(descriptor, every) {``  ``duration, err := time.ParseDuration(descriptor[len(every):])``  ``if err != nil {``   ``return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)``  ``}``  ``return Every(duration), nil`` ``}` ` ``return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)``}`

注: @every 用法比较特殊,这是Go里面比较特色的用法。同样的还有 @yearly @annually @monthly @weekly @daily @midnight @hourly 这里面就不一一赘述了。希望大家能够自己探索。


文章来源: https://cloudsjhan.github.io/2019/10/29/golang-cron%E4%BD%BF%E7%94%A8/
如有侵权请联系:admin#unsafe.sh