• go-cron源码分析(二、Parser解析器)


    2.1 Parser解析器

    接下来我们先来看看解析器,先挑软柿子捏,哈哈哈。

    还从昨天修改Parser解析器开始讲起:

    func TestWithParser(t *testing.T) {
    	var parser = NewParser(Dow)			// 这里就new了一个解析器
    	c := New(WithParser(parser))		// 这里用新的解析器替换了缺省的解析器
    	if c.parser != parser {
    		t.Error("expected provided parser")
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2 NewParser()解析

    接下来我们分析一下NewParser()函数

    // NewParser creates a Parser with custom options.
    // NewParser 创建一个 Parser with 自定义选项
    //
    // It panics if more than one Optional is given, since it would be impossible to
    // correctly infer which optional is provided or missing in general.
    // 如果给出了多个可选项,则会出现恐慌,因为一般情况下不可能正确地推断哪些可选选项被提供或缺失
    //
    // Examples
    //
    //  // 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
    //	和上面一样,只是不包括时间字段
    //  specParser := NewParser(Dom | Month | Dow)
    //  sched, err := specParser.Parse("15 */3 *")
    //
    //  // Same as above, just makes Dow optional
    //  和上面一样,只是包括了 Dow optional
    //  specParser := NewParser(Dom | Month | DowOptional)
    //  sched, err := specParser.Parse("15 */3")
    //
    func NewParser(options ParseOption) Parser {
    	optionals := 0
    	if options&DowOptional > 0 {
    		optionals++
    	}
    	if options&SecondOptional > 0 {
    		optionals++
    	}
    	if optionals > 1 {			// 这个限制只能是 DowOptional 或者是 SecondOptional 或者没有
    		panic("multiple optionals may not be configured")
    	}
    	return Parser{options}   // 生成一个解析类对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    2.2.1 ParseOption类型

    我们先来分析这个类型:

    其实这个类型是int型,我们看看源码:

    // 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   // 其实是int类型
    
    // 取值,就是每个宏占一个位,实际传参:
    // NewParser(Minute | Hour | Dom | Month | Dow)
    const (
    	Second         ParseOption = 1 << iota // Seconds field, default 0
    	SecondOptional                         // Optional 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.
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.2.2 Parser解析类

    代码最后一句话是生成一个Parser类的对象:

    // A custom Parser that can be configured.
    type Parser struct {
    	options ParseOption			// 成员就只有,选项,其他的就是成员函数了
    }
    
    • 1
    • 2
    • 3
    • 4

    2.3 func (p Parser) Parse(spec string)(Schedule, error)

    // Parse returns a new crontab schedule representing the given spec.
    // Parse返回一个新的crontab计划,表示给定的规范
    // It returns a descriptive error if the spec is not valid.
    // 如果规范无效,它将返回一个描述性错误。
    // It accepts crontab specs and features configured by NewParser.
    // 它接受NewParser配置的crontab规范和特性。
    func (p Parser) Parse(spec string) (Schedule, error) {
    	if len(spec) == 0 {
    		return nil, fmt.Errorf("empty spec string")
    	}
    
    	// Extract timezone if present
    	var loc = time.Local
    	// TZ是世界标准时间
        // 例子:TZ=Asia/Tokyo
    	if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") {
    		var err error
    		i := strings.Index(spec, " ")
    		eq := strings.Index(spec, "=")
    		// 转为对应的时间
    		if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil {
    			return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err)
    		}
    		spec = strings.TrimSpace(spec[i:])
    	}
    
    	// Handle named schedules (descriptors), if configured
    	// 处理已配置的命名计划(描述符)
    	if strings.HasPrefix(spec, "@") {
    		if p.options&Descriptor == 0 {		// 解析器需要支持描述
    			return nil, fmt.Errorf("parser does not accept descriptors: %v", spec)
    		}
    		return parseDescriptor(spec, loc)
    	}
    
    	// Split on whitespace.
    	fields := strings.Fields(spec)   // 对字符串以空格分割 例:5 * * * *
    
    	// Validate & fill in any omitted or optional fields
    	// 验证并填写任何省略或可选字段
    	var err error
    	fields, err = normalizeFields(fields, p.options)   // 验证对应的参数,并返回真正的参数,如果参数不全,会自动补全
    	if err != nil {
    		return nil, err
    	}
    
    	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
    	}
    
    	return &SpecSchedule{		// 构造一个调度器的对象
    		Second:   second,
    		Minute:   minute,
    		Hour:     hour,
    		Dom:      dayofmonth,
    		Month:    month,
    		Dow:      dayofweek,
    		Location: loc,
    	}, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    这个函数是完整的解析函数,里面包含了TZ世界标准时间和@前缀的描述进行解析。

    最后还调用了normalizeFields()这个函数来解析参数,最后的最后生成了一个调度器的对象。

    2.4 parseDescriptor(descriptor string, loc *time.Location)

    解析@前缀的时间,我们来看看这么解析的:

    // parseDescriptor returns a predefined schedule for the expression, or error if none matches.
    // parseDescriptor返回表达式的预定义调度,如果没有匹配则会出错
    func parseDescriptor(descriptor string, loc *time.Location) (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),				// 这个要好好分析一下下
    			Location: loc,
    		}, 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),
    			Location: loc,
    		}, 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,
    			Location: loc,
    		}, 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),
    			Location: loc,
    		}, 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),
    			Location: loc,
    		}, nil
    
    	}
    
        // {secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
    	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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2.4.1 all()

    all函数这么多,我们来看看里面是啥意思。

    // all returns all bits within the given bounds.  (plus the star bit)
    // All返回给定边界内的所有位。(加上星号位)
    func all(r bounds) uint64 {					// 这个初看还是有点懵逼的,这个bounds其实也是一个结构体,描述秒,分,时的范围的,我们可以来看看	
    	return getBits(r.min, r.max, 1) | starBit	// startBit是一个常量,目前也不知道有啥用	
    }
    
    // starBit
    const (
    	// Set the top bit if a star was included in the expression.
    	// 如果表达式中包含星号,则设置顶部位。
    	starBit = 1 << 63
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.4.2 bounds

    // bounds provides a range of acceptable values (plus a map of name to value).
    // Bounds提供可接受值的范围(加上名称到值的映射)。
    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,
    	}}
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    看到这里就好奇了,这么数据怎么表示我们定时的时间呢?我们继续分析

    2.4.3 getBits()

    // getBits sets all bits in the range [min, max], modulo the given step size.
    // getBits设置范围[min, max]内的所有位,取给定步长的模。
    func getBits(min, max, step uint) uint64 {
    	var bits uint64
    
    	// If step is 1, use shifts.
    	// 如果step为1,则使用移位。
    	// 我们现在传参是1
    	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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这一个流程分析完了,我们还留下了疑问,SpecSchedule类的分析,getBits()返回的这些位是有什么用?还有Every()函数返回ConstantDelaySchedule类对象?

    这个我们下节带着这些问题好好分析,接下来,我们看看传统配置的处理方式。

    2.5 normalizeFields()

    这个函数主要是判断传参是否正确,并且把参数解析成为代码默认的参数(因为有些配置只需要年月日,所以把时分秒补上)

    // normalizeFields takes a subset set of the time fields and returns the full set
    // with defaults (zeroes) populated for unset fields.
    // normalizeFields接受时间字段的子集,并返回完整的时间字段集,为未设置的字段填充默认值(零)。
    //
    // As part of performing this function, it also validates that the provided
    // 作为执行这个功能的一部分,它还验证所提供的字段是否与配置的选项兼容。
    // fields are compatible with the configured options.
    func normalizeFields(fields []string, options ParseOption) ([]string, error) {
    	// Validate optionals & add their field to options
    	optionals := 0
    	if options&SecondOptional > 0 {  // 不能很明白SecondOptional 和 Second的区别
    		options |= Second
    		optionals++
    	}
    	if options&DowOptional > 0 {
    		options |= Dow
    		optionals++
    	}
        // 这里再次判断两个不能一起出现
    	if optionals > 1 {
    		return nil, fmt.Errorf("multiple optionals may not be configured")
    	}
    
    	// Figure out how many fields we need
    	// 算出我们需要多少字段
    	max := 0
    	for _, place := range places {    // place 是自定义的值
    		if options&place > 0 {
    			max++						// 计算最大的字段
    		}
    	}
    	min := max - optionals
    
    	// Validate number of fields
    	// 验证fields是否正确
    	if count := len(fields); count < min || count > max {
    		if min == max {
    			return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields)
    		}
    		return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields)
    	}
    
    	// Populate the optional field if not provided
    	// 如果没有提供,填充可选字段。正常填充是不会进来
    	if min < max && len(fields) == min {
    		switch {
    		case options&DowOptional > 0:
    			fields = append(fields, defaults[5]) // TODO: improve access to default
    		case options&SecondOptional > 0:
    			fields = append([]string{defaults[0]}, fields...)
    		default:
    			return nil, fmt.Errorf("unknown optional field")
    		}
    	}
    
    	// Populate all fields not part of options with their defaults
    	// 用默认值填充所有不属于选项的字段
    	n := 0
    	expandedFields := make([]string, len(places))
    	copy(expandedFields, defaults)		//先以缺省的字段填充
    	for i, place := range places {
    		if options&place > 0 {  // 如果没有这个字段,expandedFields就是默认值
    			expandedFields[i] = fields[n]   // 填充匹配的字段的值
    			n++
    		}
    	}
    	return expandedFields, nil
    }
    
    // places就是平常的6个
    var places = []ParseOption{
    	Second,
    	Minute,
    	Hour,
    	Dom,
    	Month,
    	Dow,
    }
    
    // defaults对应默认值
    var defaults = []string{
    	"0",
    	"0",
    	"0",
    	"*",
    	"*",
    	"*",
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    2.5.1 field匿名函数

    我们接着来分析这个匿名函数,

    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)
    	)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.5.2 getField()

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.5.3 getRange()

    分析传参,并且通过参数返回对应的位数据

    // getRange returns the bits indicated by the given expression:
    // getRange返回由给定表达式指定的位:
    //   number | number "-" number [ "/" number ]
    // or error parsing range.
    func getRange(expr string, r bounds) (uint64, error) {
    	var (
    		start, end, step uint
    		// */2		需要解析分号情况
    		rangeAndStep     = strings.Split(expr, "/")
    		// 5-6/2    需要直接-
    		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)		// 如果是 5-6这种, 范围就是 5 到 6
    			if err != nil {
    				return 0, err
    			}
    		default:
    			return 0, fmt.Errorf("too many hyphens: %s", expr)
    		}
    	}
    
    	switch len(rangeAndStep) {		// 看看有没有 5/6 这种情况
    	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
    		}
    		if step > 1 {
    			extra = 0
    		}
    	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    // 这个步进在这里用的
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2.5.4 其他工具函数

    // parseIntOrName returns the (possibly-named) integer contained in expr.
    // 解析字符串for name,如果有names这个字段,就直接在map中取数据
    func parseIntOrName(expr string, names map[string]uint) (uint, error) {
    	if names != nil {
    		if namedInt, ok := names[strings.ToLower(expr)]; ok {   // 如果是有names的,就直接在map,取值
    			return namedInt, nil
    		}
    	}
    	return mustParseInt(expr)   // 剩下的就只有秒 分 时
    }
    
    // mustParseInt parses the given expression as an int or returns an error.
    // 这个就直接字符串转int了
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.6 standardParser

    其实源码中默认定义了一个标准解析器,如果我们没有传新的解析器,系统就会默认使用这个。

    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 requires 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"
    func ParseStandard(standardSpec string) (Schedule, error) {
    	return standardParser.Parse(standardSpec)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    默认解析器是没有秒的,这也是linux的cron的定时,也是没有秒的,所以我们如果需要秒的定时,就需要自定义解析器。这个看上一节就明白了。

    2.7 总结

    这一节主要介绍了解析器,可以自定义解析器。

    1. 系统始终是6位的解析器,如果自定义解析器减少了某一个,系统会自动补成缺省值。
    2. 解析器有两种格式,一种是@xxx 一种是 x x x x x。两种解析走不同分支
    3. 不管走那个分支,最后都是生成一个SpecSchedule对象或者ConstantDelaySchedule对象,这个这一节暂不分析
    4. 生成SpecSchedule对象,都需要转参,并且参数是一堆按位操作的数,这些数,这一节也暂不分析。

    带着这两个问题,期待下一节分析。

  • 相关阅读:
    容器云平台的六个最佳实践
    WebSocket开发(记录落地)功能
    死锁问题【javaEE初阶】
    y80.第四章 Prometheus大厂监控体系及实战 -- kube-state-metrics组件介绍和监控扩展(十一)
    SpringBoot 常用注解的原理和使用
    精彩预告 | OpenHarmony即将亮相MTSC 2023
    修复画笔工具组
    (缺省参数)&(函数重载)&(引用)&(内敛)&(C++中的nullptr)
    【Pytorch 】Dataset 和Dataloader制作数据集
    有什么可替代问卷星的好用的问卷工具?
  • 原文地址:https://blog.csdn.net/C1033177205/article/details/127760598