What is cron?
cron means: to plan tasks, to put it plainly, is to schedule tasks. I have an appointment with the system. It's as simple as running a task at a few minutes or seconds or every few minutes.
cron expression
Cron expression is a good thing. It can be used not only in Java quartZ, but also in Go. I haven't used cron in Linux, but the Internet says that Linux can also use crontab -e command to configure timing tasks. Both the Go language and Java can be accurate to seconds, but not in Linux.
The cron expression represents a collection of times and is represented by fields separated by six spaces:
Field name | Is it necessary? | Permissible values | Permissible specific characters |
Seconds (Seconds) | yes | 0-59 | * / , - |
Sub (Minute) | yes | 0-59 | * / , - |
Time (Hours) | yes | 0-23 | * / , - |
Day of month | yes | 1-31 | * / , - ? |
Month (Month) | yes | 1-12 or JAN-DEC | * / , - |
Day of week | no | 0-6 or SUM-SAT | * / , - ? |
Note:
The values of the Month and Day of week fields are case-insensitive, such as sun, sun and sun.
2. If the Day of week field is not provided, it is equivalent to*
# ┌───────────── min (0 - 59) # │ ┌────────────── hour (0 - 23) # │ │ ┌─────────────── day of month (1 - 31) # │ │ │ ┌──────────────── month (1 - 12) # │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to # │ │ │ │ │ Saturday, or use names; 7 is also Sunday) # │ │ │ │ │ # │ │ │ │ │ # * * * * * command to execute
cron Specific Character Description
1) asterisk (*)
Represents that the cron expression matches all the values of the field. For example, in the fifth field, an asterisk (month) is used to indicate each month.
2) diagonal (/)
For growth intervals, if the value of the first field (minutes) is 3-59/15, it means that the execution starts at the 3rd minute of an hour and then every 15 minutes (i.e. at 3, 18, 33, 48), which can also be expressed as: 3/15
3) comma (,)
Enumeration values, such as field 6 values MON,WED,FRI, for Monday, Wednesday, and Five
4) hyphen (-)
Represents a range, such as a value of 9-17 for the third field, which means 9 am to 5 pm per hour (including 9 and 17)
5) Question mark (?)
Use only for days of month and weeks, meaning no specified value, and can be used instead.*
6)L,W,#
There is no use of L, W, # in Go, as explained below.
An example of cron
Execute every five seconds: */5* * * * *?
Execute every 1 minute: 0 */1 * * *?
Execution at 23:00 a day: 0 023 * *?
Execution at 1:00 a.m. every day: 0.01 * *?
Execution at 1:00 a.m. on the 1st of each month: 0.01 1*?
One execution at 26, 29 and 33 points: 0.26, 29, 33* * *?
Every day at 0:00, 13:00, 18:00 and 21:00: 00, 13,18,21*?
Download and install
The console enters go get github.com/robfig/cron to download the Go package for the timing task, provided your $GOPATH is configured.
Source code analysis
Explanation of File Catalogue
1 constantdelay.go #A simple second-level timing system. and cron Irrelevant 2 constantdelay_test.go #test 3 cron.go #Cron System. Managing a series of cron Timing tasks( Schedule Job) 4 cron_test.go #test 5 doc.go #Description document 6 LICENSE #Certificate of authorization 7 parser.go #Parser, parsing cron Format String City A Specific Timer( Schedule) 8 parser_test.go #test 9 README.md #README 10 spec.go #Single timer( Schedule)Structure. How to calculate your next trigger time 11 spec_test.go #test
cron.go
Structure:
1 // Cron keeps track of any number of entries, invoking the associated func as 2 // specified by the schedule. It may be started, stopped, and the entries may 3 // be inspected while running. 4 // Cron Keep track of any number of entries and call related func Schedule specified. It can be started, stopped and entries that can be run while checking. 5 type Cron struct { 6 entries []*Entry // task 7 stop chan struct{} // The Way to Stop 8 add chan *Entry // How to add new tasks 9 snapshot chan []*Entry // How to request a task snapshot 10 running bool // Is it running? 11 ErrorLog *log.Logger // error log(New attribute) 12 location *time.Location // Location(New attribute) 13 }
1 // Entry consists of a schedule and the func to execute on that schedule. 2 // Entry includes timetables and can be executed on timetables func 3 type Entry struct { 4 // timer 5 Schedule Schedule 6 // Next execution time 7 Next time.Time 8 // Last execution time 9 Prev time.Time 10 // task 11 Job Job 12 }
Key methodologies:
1 // Start task 2 // Start the cron scheduler in its own go-routine, or no-op if already started. 3 func (c *Cron) Start() { 4 if c.running { 5 return 6 } 7 c.running = true 8 go c.run() 9 } 10 // End task 11 // Stop stops the cron scheduler if it is running; otherwise it does nothing. 12 func (c *Cron) Stop() { 13 if !c.running { 14 return 15 } 16 c.stop <- struct{}{} 17 c.running = false 18 } 19 20 // Perform timed tasks 21 // Run the scheduler.. this is private just due to the need to synchronize 22 // access to the 'running' state variable. 23 func (c *Cron) run() { 24 // Figure out the next activation times for each entry. 25 now := time.Now().In(c.location) 26 for _, entry := range c.entries { 27 entry.Next = entry.Schedule.Next(now) 28 } 29 // Infinite cycle 30 for { 31 //By sorting the next execution time, determine which tasks will be executed next time, and guard against the front of the queue.sort It's used for sorting. 32 sort.Sort(byTime(c.entries)) 33 34 var effective time.Time 35 if len(c.entries) == 0 || c.entries[0].Next.IsZero() { 36 // If there are no entries yet, just sleep - it still handles new entries 37 // and stop requests. 38 effective = now.AddDate(10, 0, 0) 39 } else { 40 effective = c.entries[0].Next 41 } 42 43 timer := time.NewTimer(effective.Sub(now)) 44 select { 45 case now = <-timer.C: // Execute current tasks 46 now = now.In(c.location) 47 // Run every entry whose next time was this effective time. 48 for _, e := range c.entries { 49 if e.Next != effective { 50 break 51 } 52 go c.runWithRecovery(e.Job) 53 e.Prev = e.Next 54 e.Next = e.Schedule.Next(now) 55 } 56 continue 57 58 case newEntry := <-c.add: // Add new tasks 59 c.entries = append(c.entries, newEntry) 60 newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location)) 61 62 case <-c.snapshot: // Take Snapshot 63 c.snapshot <- c.entrySnapshot() 64 65 case <-c.stop: // Stop task 66 timer.Stop() 67 return 68 } 69 70 // 'now' should be updated after newEntry and snapshot cases. 71 now = time.Now().In(c.location) 72 timer.Stop() 73 } 74 }
spec.go
Structures and key methods:
1 // SpecSchedule specifies a duty cycle (to the second granularity), based on a 2 // traditional crontab specification. It is computed initially and stored as bit sets. 3 type SpecSchedule struct { 4 // Locks in expressions indicate seconds, minutes, hours, days, months, weeks, each of which is uint64 5 // Dom:Day of Month,Dow:Day of week 6 Second, Minute, Hour, Dom, Month, Dow uint64 7 } 8 9 // bounds provides a range of acceptable values (plus a map of name to value). 10 // The structure of an expression is defined. 11 type bounds struct { 12 min, max uint 13 names map[string]uint 14 } 15 16 17 // The bounds for each field. 18 // So you can see the scope of each expression. 19 var ( 20 seconds = bounds{0, 59, nil} 21 minutes = bounds{0, 59, nil} 22 hours = bounds{0, 23, nil} 23 dom = bounds{1, 31, nil} 24 months = bounds{1, 12, map[string]uint{ 25 "jan": 1, 26 "feb": 2, 27 "mar": 3, 28 "apr": 4, 29 "may": 5, 30 "jun": 6, 31 "jul": 7, 32 "aug": 8, 33 "sep": 9, 34 "oct": 10, 35 "nov": 11, 36 "dec": 12, 37 }} 38 dow = bounds{0, 6, map[string]uint{ 39 "sun": 0, 40 "mon": 1, 41 "tue": 2, 42 "wed": 3, 43 "thu": 4, 44 "fri": 5, 45 "sat": 6, 46 }} 47 ) 48 49 const ( 50 // Set the top bit if a star was included in the expression. 51 starBit = 1 << 63 52 )
Looking at the above, one must wonder why these definitions are unit64 and a constant starBit = 1 << 63, which is a logical operator. Represents that binary 1 moves 63 bits to the left. The reasons are as follows:
Cron expression is used to express a series of time, and time can not escape its own interval, minute, second 0 - 59, hour 0 - 23, day / month 0 - 31, day / week 0 - 6, month 0 - 11. These are essentially a set of points, or an integer interval. Then for any integer interval, the following rules of cron can be described.
- *| Arbitrary, corresponding to all points in the interval. (Pay extra attention to day/week, day/month interference.)
- Pure numbers correspond to a specific point.
- / The two digits a, b, which are divided, correspond to all points of a + n * b on the interval (n >= 0).
- - The two digits divided correspond to all points in the interval determined by the two digits.
- L | W needs to make special judgments about specific time, which can not be used to correspond to the points on the interval.
So far, the reason why robfig/cron does not support L | W is clear. After removing these two rules, the rest of the rules can be expressed by exhaustion of points. Considering that the maximum interval is only 60 points, it is appropriate to use each bit of an uint64 integer to represent a point. So it's not too much to define unit64.
The following is the method of cron expression in go:
/* ------------------------------------------------------------ The 64-bit mark is arbitrary and is used for day/week and day/month interference. 63 - 0 represents each point of the interval [63, 0]. ------------------------------------------------------------ Assuming that the interval is 0 - 63, there are the following examples: For example, 0/3 is expressed as follows: (1 every two) * / ? +---+--------------------------------------------------------+ | 0 | 1 0 0 1 0 0 1 ~~ ~~ 1 0 0 1 0 0 1 | +---+--------------------------------------------------------+ 63 ~ ~ ~~ 0 For example, 2-5 is expressed as follows: (1) from right to left, 2-5 bits are all 1) * / ? +---+--------------------------------------------------------+ | 0 | 0 0 0 0 ~ ~ ~~ ~ 0 0 0 1 1 1 1 0 0 | +---+--------------------------------------------------------+ 63 ~ ~ ~~ 0 For example, * is expressed as follows: (1 for all positions) * / ? +---+--------------------------------------------------------+ | 1 | 1 1 1 1 1 ~ ~ ~ 1 1 1 1 1 1 1 1 1 | +---+--------------------------------------------------------+ 63 ~ ~ ~~ 0 */
parser.go
Resolve strings into SpecSchedule classes.
1 package cron 2 3 import ( 4 "fmt" 5 "math" 6 "strconv" 7 "strings" 8 "time" 9 ) 10 11 // Configuration options for creating a parser. Most options specify which 12 // fields should be included, while others enable features. If a field is not 13 // included the parser will assume a default value. These options do not change 14 // the order fields are parse in. 15 type ParseOption int 16 17 const ( 18 Second ParseOption = 1 << iota // Seconds field, default 0 19 Minute // Minutes field, default 0 20 Hour // Hours field, default 0 21 Dom // Day of month field, default * 22 Month // Month field, default * 23 Dow // Day of week field, default * 24 DowOptional // Optional day of week field, default * 25 Descriptor // Allow descriptors such as @monthly, @weekly, etc. 26 ) 27 28 var places = []ParseOption{ 29 Second, 30 Minute, 31 Hour, 32 Dom, 33 Month, 34 Dow, 35 } 36 37 var defaults = []string{ 38 "0", 39 "0", 40 "0", 41 "*", 42 "*", 43 "*", 44 } 45 46 // A custom Parser that can be configured. 47 type Parser struct { 48 options ParseOption 49 optionals int 50 } 51 52 // Creates a custom Parser with custom options. 53 // 54 // // Standard parser without descriptors 55 // specParser := NewParser(Minute | Hour | Dom | Month | Dow) 56 // sched, err := specParser.Parse("0 0 15 */3 *") 57 // 58 // // Same as above, just excludes time fields 59 // subsParser := NewParser(Dom | Month | Dow) 60 // sched, err := specParser.Parse("15 */3 *") 61 // 62 // // Same as above, just makes Dow optional 63 // subsParser := NewParser(Dom | Month | DowOptional) 64 // sched, err := specParser.Parse("15 */3") 65 // 66 func NewParser(options ParseOption) Parser { 67 optionals := 0 68 if options&DowOptional > 0 { 69 options |= Dow 70 optionals++ 71 } 72 return Parser{options, optionals} 73 } 74 75 // Parse returns a new crontab schedule representing the given spec. 76 // It returns a descriptive error if the spec is not valid. 77 // It accepts crontab specs and features configured by NewParser. 78 // Resolve strings into SpecSchedule . SpecSchedule accord with Schedule Interface 79 80 func (p Parser) Parse(spec string) (Schedule, error) { 81 // Direct processing of special strings 82 if spec[0] == '@' && p.options&Descriptor > 0 { 83 return parseDescriptor(spec) 84 } 85 86 // Figure out how many fields we need 87 max := 0 88 for _, place := range places { 89 if p.options&place > 0 { 90 max++ 91 } 92 } 93 min := max - p.optionals 94 95 // cron Use blanks to disassemble independent items. 96 fields := strings.Fields(spec) 97 98 // Validation expression range 99 if count := len(fields); count < min || count > max { 100 if min == max { 101 return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec) 102 } 103 return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec) 104 } 105 106 // Fill in missing fields 107 fields = expandFields(fields, p.options) 108 109 var err error 110 field := func(field string, r bounds) uint64 { 111 if err != nil { 112 return 0 113 } 114 var bits uint64 115 bits, err = getField(field, r) 116 return bits 117 } 118 119 var ( 120 second = field(fields[0], seconds) 121 minute = field(fields[1], minutes) 122 hour = field(fields[2], hours) 123 dayofmonth = field(fields[3], dom) 124 month = field(fields[4], months) 125 dayofweek = field(fields[5], dow) 126 ) 127 if err != nil { 128 return nil, err 129 } 130 // Return the required SpecSchedule 131 return &SpecSchedule{ 132 Second: second, 133 Minute: minute, 134 Hour: hour, 135 Dom: dayofmonth, 136 Month: month, 137 Dow: dayofweek, 138 }, nil 139 } 140 141 func expandFields(fields []string, options ParseOption) []string { 142 n := 0 143 count := len(fields) 144 expFields := make([]string, len(places)) 145 copy(expFields, defaults) 146 for i, place := range places { 147 if options&place > 0 { 148 expFields[i] = fields[n] 149 n++ 150 } 151 if n == count { 152 break 153 } 154 } 155 return expFields 156 } 157 158 var standardParser = NewParser( 159 Minute | Hour | Dom | Month | Dow | Descriptor, 160 ) 161 162 // ParseStandard returns a new crontab schedule representing the given standardSpec 163 // (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always 164 // pass 5 entries representing: minute, hour, day of month, month and day of week, 165 // in that order. It returns a descriptive error if the spec is not valid. 166 // 167 // It accepts 168 // - Standard crontab specs, e.g. "* * * * ?" 169 // - Descriptors, e.g. "@midnight", "@every 1h30m" 170 // This means that not only can you use it cron Expressions can also be used@midnight @every Other methods 171 172 func ParseStandard(standardSpec string) (Schedule, error) { 173 return standardParser.Parse(standardSpec) 174 } 175 176 var defaultParser = NewParser( 177 Second | Minute | Hour | Dom | Month | DowOptional | Descriptor, 178 ) 179 180 // Parse returns a new crontab schedule representing the given spec. 181 // It returns a descriptive error if the spec is not valid. 182 // 183 // It accepts 184 // - Full crontab specs, e.g. "* * * * * ?" 185 // - Descriptors, e.g. "@midnight", "@every 1h30m" 186 func Parse(spec string) (Schedule, error) { 187 return defaultParser.Parse(spec) 188 } 189 190 // getField returns an Int with the bits set representing all of the times that 191 // the field represents or error parsing field value. A "field" is a comma-separated 192 // list of "ranges". 193 func getField(field string, r bounds) (uint64, error) { 194 var bits uint64 195 ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) 196 for _, expr := range ranges { 197 bit, err := getRange(expr, r) 198 if err != nil { 199 return bits, err 200 } 201 bits |= bit 202 } 203 return bits, nil 204 } 205 206 // getRange returns the bits indicated by the given expression: 207 // number | number "-" number [ "/" number ] 208 // or error parsing range. 209 func getRange(expr string, r bounds) (uint64, error) { 210 var ( 211 start, end, step uint 212 rangeAndStep = strings.Split(expr, "/") 213 lowAndHigh = strings.Split(rangeAndStep[0], "-") 214 singleDigit = len(lowAndHigh) == 1 215 err error 216 ) 217 218 var extra uint64 219 if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { 220 start = r.min 221 end = r.max 222 extra = starBit 223 } else { 224 start, err = parseIntOrName(lowAndHigh[0], r.names) 225 if err != nil { 226 return 0, err 227 } 228 switch len(lowAndHigh) { 229 case 1: 230 end = start 231 case 2: 232 end, err = parseIntOrName(lowAndHigh[1], r.names) 233 if err != nil { 234 return 0, err 235 } 236 default: 237 return 0, fmt.Errorf("Too many hyphens: %s", expr) 238 } 239 } 240 241 switch len(rangeAndStep) { 242 case 1: 243 step = 1 244 case 2: 245 step, err = mustParseInt(rangeAndStep[1]) 246 if err != nil { 247 return 0, err 248 } 249 250 // Special handling: "N/step" means "N-max/step". 251 if singleDigit { 252 end = r.max 253 } 254 default: 255 return 0, fmt.Errorf("Too many slashes: %s", expr) 256 } 257 258 if start < r.min { 259 return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr) 260 } 261 if end > r.max { 262 return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr) 263 } 264 if start > end { 265 return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr) 266 } 267 if step == 0 { 268 return 0, fmt.Errorf("Step of range should be a positive number: %s", expr) 269 } 270 271 return getBits(start, end, step) | extra, nil 272 } 273 274 // parseIntOrName returns the (possibly-named) integer contained in expr. 275 func parseIntOrName(expr string, names map[string]uint) (uint, error) { 276 if names != nil { 277 if namedInt, ok := names[strings.ToLower(expr)]; ok { 278 return namedInt, nil 279 } 280 } 281 return mustParseInt(expr) 282 } 283 284 // mustParseInt parses the given expression as an int or returns an error. 285 func mustParseInt(expr string) (uint, error) { 286 num, err := strconv.Atoi(expr) 287 if err != nil { 288 return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err) 289 } 290 if num < 0 { 291 return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr) 292 } 293 294 return uint(num), nil 295 } 296 297 // getBits sets all bits in the range [min, max], modulo the given step size. 298 func getBits(min, max, step uint) uint64 { 299 var bits uint64 300 301 // If step is 1, use shifts. 302 if step == 1 { 303 return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) 304 } 305 306 // Else, use a simple loop. 307 for i := min; i <= max; i += step { 308 bits |= 1 << i 309 } 310 return bits 311 } 312 313 // all returns all bits within the given bounds. (plus the star bit) 314 func all(r bounds) uint64 { 315 return getBits(r.min, r.max, 1) | starBit 316 } 317 318 // parseDescriptor returns a predefined schedule for the expression, or error if none matches. 319 func parseDescriptor(descriptor string) (Schedule, error) { 320 switch descriptor { 321 case "@yearly", "@annually": 322 return &SpecSchedule{ 323 Second: 1 << seconds.min, 324 Minute: 1 << minutes.min, 325 Hour: 1 << hours.min, 326 Dom: 1 << dom.min, 327 Month: 1 << months.min, 328 Dow: all(dow), 329 }, nil 330 331 case "@monthly": 332 return &SpecSchedule{ 333 Second: 1 << seconds.min, 334 Minute: 1 << minutes.min, 335 Hour: 1 << hours.min, 336 Dom: 1 << dom.min, 337 Month: all(months), 338 Dow: all(dow), 339 }, nil 340 341 case "@weekly": 342 return &SpecSchedule{ 343 Second: 1 << seconds.min, 344 Minute: 1 << minutes.min, 345 Hour: 1 << hours.min, 346 Dom: all(dom), 347 Month: all(months), 348 Dow: 1 << dow.min, 349 }, nil 350 351 case "@daily", "@midnight": 352 return &SpecSchedule{ 353 Second: 1 << seconds.min, 354 Minute: 1 << minutes.min, 355 Hour: 1 << hours.min, 356 Dom: all(dom), 357 Month: all(months), 358 Dow: all(dow), 359 }, nil 360 361 case "@hourly": 362 return &SpecSchedule{ 363 Second: 1 << seconds.min, 364 Minute: 1 << minutes.min, 365 Hour: all(hours), 366 Dom: all(dom), 367 Month: all(months), 368 Dow: all(dow), 369 }, nil 370 } 371 372 const every = "@every " 373 if strings.HasPrefix(descriptor, every) { 374 duration, err := time.ParseDuration(descriptor[len(every):]) 375 if err != nil { 376 return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err) 377 } 378 return Every(duration), nil 379 } 380 381 return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor) 382 }
Project application
package main import ( "github.com/robfig/cron" "log" ) func main() { i := 0 c := cron.New() spec := "*/5 * * * * ?" c.AddFunc(spec, func() { i++ log.Println("cron running:", i) }) c.AddFunc("@every 1h1m", func() { i++ log.Println("cron running:", i) }) c.Start() }
Note: @every is a special usage, which is a characteristic usage in Go. The same goes for @yearly@annually@monthly@weekly@daily@midnight@hourly. I hope you can explore for yourselves.
Reference website:
http://blog.studygolang.com/2014/02/go_crontab/
http://blog.csdn.net/cchd0001/article/details/51076922