package cron
import (
)
type ParseOption int
const (
Second ParseOption = 1 << iota
SecondOptional
Minute
Hour
Dom
Month
Dow
DowOptional
Descriptor
)
var places = []ParseOption{
Second,
Minute,
Hour,
Dom,
Month,
Dow,
}
var defaults = []string{
"0",
"0",
"0",
"*",
"*",
"*",
}
type Parser struct {
options ParseOption
}
func ( ParseOption) Parser {
:= 0
if &DowOptional > 0 {
++
}
if &SecondOptional > 0 {
++
}
if > 1 {
panic("multiple optionals may not be configured")
}
return Parser{}
}
func ( Parser) ( string) (Schedule, error) {
if len() == 0 {
return nil, fmt.Errorf("empty spec string")
}
var = time.Local
if strings.HasPrefix(, "TZ=") || strings.HasPrefix(, "CRON_TZ=") {
var error
:= strings.Index(, " ")
:= strings.Index(, "=")
if , = time.LoadLocation([+1 : ]); != nil {
return nil, fmt.Errorf("provided bad location %s: %v", [+1:], )
}
= strings.TrimSpace([:])
}
if strings.HasPrefix(, "@") {
if .options&Descriptor == 0 {
return nil, fmt.Errorf("parser does not accept descriptors: %v", )
}
return parseDescriptor(, )
}
:= strings.Fields()
var error
, = normalizeFields(, .options)
if != nil {
return nil,
}
:= func( string, bounds) uint64 {
if != nil {
return 0
}
var uint64
, = getField(, )
return
}
var (
= ([0], seconds)
= ([1], minutes)
= ([2], hours)
= ([3], dom)
= ([4], months)
= ([5], dow)
)
if != nil {
return nil,
}
return &SpecSchedule{
Second: ,
Minute: ,
Hour: ,
Dom: ,
Month: ,
Dow: ,
Location: ,
}, nil
}
func ( []string, ParseOption) ([]string, error) {
:= 0
if &SecondOptional > 0 {
|= Second
++
}
if &DowOptional > 0 {
|= Dow
++
}
if > 1 {
return nil, fmt.Errorf("multiple optionals may not be configured")
}
:= 0
for , := range places {
if & > 0 {
++
}
}
:= -
if := len(); < || > {
if == {
return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", , , )
}
return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", , , , )
}
if < && len() == {
switch {
case &DowOptional > 0:
= append(, defaults[5])
case &SecondOptional > 0:
= append([]string{defaults[0]}, ...)
default:
return nil, fmt.Errorf("unknown optional field")
}
}
:= 0
:= make([]string, len(places))
copy(, defaults)
for , := range places {
if & > 0 {
[] = []
++
}
}
return , nil
}
var standardParser = NewParser(
Minute | Hour | Dom | Month | Dow | Descriptor,
)
func ( string) (Schedule, error) {
return standardParser.Parse()
}
func ( string, bounds) (uint64, error) {
var uint64
:= strings.FieldsFunc(, func( rune) bool { return == ',' })
for , := range {
, := getRange(, )
if != nil {
return ,
}
|=
}
return , nil
}
func ( string, bounds) (uint64, error) {
var (
, , uint
= strings.Split(, "/")
= strings.Split([0], "-")
= len() == 1
error
)
var uint64
if [0] == "*" || [0] == "?" {
= .min
= .max
= starBit
} else {
, = parseIntOrName([0], .names)
if != nil {
return 0,
}
switch len() {
case 1:
=
case 2:
, = parseIntOrName([1], .names)
if != nil {
return 0,
}
default:
return 0, fmt.Errorf("too many hyphens: %s", )
}
}
switch len() {
case 1:
= 1
case 2:
, = mustParseInt([1])
if != nil {
return 0,
}
if {
= .max
}
if > 1 {
= 0
}
default:
return 0, fmt.Errorf("too many slashes: %s", )
}
if < .min {
return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", , .min, )
}
if > .max {
return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", , .max, )
}
if > {
return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", , , )
}
if == 0 {
return 0, fmt.Errorf("step of range should be a positive number: %s", )
}
return getBits(, , ) | , nil
}
func ( string, map[string]uint) (uint, error) {
if != nil {
if , := [strings.ToLower()]; {
return , nil
}
}
return mustParseInt()
}
func ( string) (uint, error) {
, := strconv.Atoi()
if != nil {
return 0, fmt.Errorf("failed to parse int from %s: %s", , )
}
if < 0 {
return 0, fmt.Errorf("negative number (%d) not allowed: %s", , )
}
return uint(), nil
}
func (, , uint) uint64 {
var uint64
if == 1 {
return ^(math.MaxUint64 << ( + 1)) & (math.MaxUint64 << )
}
for := ; <= ; += {
|= 1 <<
}
return
}
func ( bounds) uint64 {
return getBits(.min, .max, 1) | starBit
}
func ( string, *time.Location) (Schedule, error) {
switch {
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: ,
}, 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: ,
}, 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: ,
}, 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: ,
}, 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: ,
}, nil
}
const = "@every "
if strings.HasPrefix(, ) {
, := time.ParseDuration([len():])
if != nil {
return nil, fmt.Errorf("failed to parse duration %s: %s", , )
}
return Every(), nil
}
return nil, fmt.Errorf("unrecognized descriptor: %s", )
}