mirror of
https://github.com/duanhf2012/origin.git
synced 2026-02-03 22:45:13 +08:00
提交origin2.0版本
This commit is contained in:
268
util/timer/cronexpr.go
Normal file
268
util/timer/cronexpr.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package timer
|
||||
|
||||
// reference: https://github.com/robfig/cron
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Field name | Mandatory? | Allowed values | Allowed special characters
|
||||
// ---------- | ---------- | -------------- | --------------------------
|
||||
// Seconds | No | 0-59 | * / , -
|
||||
// Minutes | Yes | 0-59 | * / , -
|
||||
// Hours | Yes | 0-23 | * / , -
|
||||
// Day of month | Yes | 1-31 | * / , -
|
||||
// Month | Yes | 1-12 | * / , -
|
||||
// Day of week | Yes | 0-6 | * / , -
|
||||
type CronExpr struct {
|
||||
sec uint64
|
||||
min uint64
|
||||
hour uint64
|
||||
dom uint64
|
||||
month uint64
|
||||
dow uint64
|
||||
}
|
||||
|
||||
// goroutine safe
|
||||
func NewCronExpr(expr string) (cronExpr *CronExpr, err error) {
|
||||
fields := strings.Fields(expr)
|
||||
if len(fields) != 5 && len(fields) != 6 {
|
||||
err = fmt.Errorf("invalid expr %v: expected 5 or 6 fields, got %v", expr, len(fields))
|
||||
return
|
||||
}
|
||||
|
||||
if len(fields) == 5 {
|
||||
fields = append([]string{"0"}, fields...)
|
||||
}
|
||||
|
||||
cronExpr = new(CronExpr)
|
||||
// Seconds
|
||||
cronExpr.sec, err = parseCronField(fields[0], 0, 59)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
// Minutes
|
||||
cronExpr.min, err = parseCronField(fields[1], 0, 59)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
// Hours
|
||||
cronExpr.hour, err = parseCronField(fields[2], 0, 23)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
// Day of month
|
||||
cronExpr.dom, err = parseCronField(fields[3], 1, 31)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
// Month
|
||||
cronExpr.month, err = parseCronField(fields[4], 1, 12)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
// Day of week
|
||||
cronExpr.dow, err = parseCronField(fields[5], 0, 6)
|
||||
if err != nil {
|
||||
goto onError
|
||||
}
|
||||
return
|
||||
|
||||
onError:
|
||||
err = fmt.Errorf("invalid expr %v: %v", expr, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 1. *
|
||||
// 2. num
|
||||
// 3. num-num
|
||||
// 4. */num
|
||||
// 5. num/num (means num-max/num)
|
||||
// 6. num-num/num
|
||||
func parseCronField(field string, min int, max int) (cronField uint64, err error) {
|
||||
fields := strings.Split(field, ",")
|
||||
for _, field := range fields {
|
||||
rangeAndIncr := strings.Split(field, "/")
|
||||
if len(rangeAndIncr) > 2 {
|
||||
err = fmt.Errorf("too many slashes: %v", field)
|
||||
return
|
||||
}
|
||||
|
||||
// range
|
||||
startAndEnd := strings.Split(rangeAndIncr[0], "-")
|
||||
if len(startAndEnd) > 2 {
|
||||
err = fmt.Errorf("too many hyphens: %v", rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
|
||||
var start, end int
|
||||
if startAndEnd[0] == "*" {
|
||||
if len(startAndEnd) != 1 {
|
||||
err = fmt.Errorf("invalid range: %v", rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
start = min
|
||||
end = max
|
||||
} else {
|
||||
// start
|
||||
start, err = strconv.Atoi(startAndEnd[0])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid range: %v", rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
// end
|
||||
if len(startAndEnd) == 1 {
|
||||
if len(rangeAndIncr) == 2 {
|
||||
end = max
|
||||
} else {
|
||||
end = start
|
||||
}
|
||||
} else {
|
||||
end, err = strconv.Atoi(startAndEnd[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid range: %v", rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if start > end {
|
||||
err = fmt.Errorf("invalid range: %v", rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
if start < min {
|
||||
err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
if end > max {
|
||||
err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0])
|
||||
return
|
||||
}
|
||||
|
||||
// increment
|
||||
var incr int
|
||||
if len(rangeAndIncr) == 1 {
|
||||
incr = 1
|
||||
} else {
|
||||
incr, err = strconv.Atoi(rangeAndIncr[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1])
|
||||
return
|
||||
}
|
||||
if incr <= 0 {
|
||||
err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// cronField
|
||||
if incr == 1 {
|
||||
cronField |= ^(math.MaxUint64 << uint(end+1)) & (math.MaxUint64 << uint(start))
|
||||
} else {
|
||||
for i := start; i <= end; i += incr {
|
||||
cronField |= 1 << uint(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e *CronExpr) matchDay(t time.Time) bool {
|
||||
// day-of-month blank
|
||||
if e.dom == 0xfffffffe {
|
||||
return 1<<uint(t.Weekday())&e.dow != 0
|
||||
}
|
||||
|
||||
// day-of-week blank
|
||||
if e.dow == 0x7f {
|
||||
return 1<<uint(t.Day())&e.dom != 0
|
||||
}
|
||||
|
||||
return 1<<uint(t.Weekday())&e.dow != 0 ||
|
||||
1<<uint(t.Day())&e.dom != 0
|
||||
}
|
||||
|
||||
// goroutine safe
|
||||
func (e *CronExpr) Next(t time.Time) time.Time {
|
||||
// the upcoming second
|
||||
t = t.Truncate(time.Second).Add(time.Second)
|
||||
|
||||
year := t.Year()
|
||||
initFlag := false
|
||||
|
||||
retry:
|
||||
// Year
|
||||
if t.Year() > year+1 {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Month
|
||||
for 1<<uint(t.Month())&e.month == 0 {
|
||||
if !initFlag {
|
||||
initFlag = true
|
||||
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
t = t.AddDate(0, 1, 0)
|
||||
if t.Month() == time.January {
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
// Day
|
||||
for !e.matchDay(t) {
|
||||
if !initFlag {
|
||||
initFlag = true
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
t = t.AddDate(0, 0, 1)
|
||||
if t.Day() == 1 {
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
// Hours
|
||||
for 1<<uint(t.Hour())&e.hour == 0 {
|
||||
if !initFlag {
|
||||
initFlag = true
|
||||
t = t.Truncate(time.Hour)
|
||||
}
|
||||
|
||||
t = t.Add(time.Hour)
|
||||
if t.Hour() == 0 {
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
// Minutes
|
||||
for 1<<uint(t.Minute())&e.min == 0 {
|
||||
if !initFlag {
|
||||
initFlag = true
|
||||
t = t.Truncate(time.Minute)
|
||||
}
|
||||
|
||||
t = t.Add(time.Minute)
|
||||
if t.Minute() == 0 {
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
// Seconds
|
||||
for 1<<uint(t.Second())&e.sec == 0 {
|
||||
if !initFlag {
|
||||
initFlag = true
|
||||
}
|
||||
|
||||
t = t.Add(time.Second)
|
||||
if t.Second() == 0 {
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
67
util/timer/example_test.go
Normal file
67
util/timer/example_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package timer_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/name5566/leaf/timer"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleTimer() {
|
||||
d := timer.NewDispatcher(10)
|
||||
|
||||
// timer 1
|
||||
d.AfterFunc(1, func() {
|
||||
fmt.Println("My name is Leaf")
|
||||
})
|
||||
|
||||
// timer 2
|
||||
t := d.AfterFunc(1, func() {
|
||||
fmt.Println("will not print")
|
||||
})
|
||||
t.Stop()
|
||||
|
||||
// dispatch
|
||||
(<-d.ChanTimer).Cb()
|
||||
|
||||
// Output:
|
||||
// My name is Leaf
|
||||
}
|
||||
|
||||
func ExampleCronExpr() {
|
||||
cronExpr, err := timer.NewCronExpr("0 * * * *")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(cronExpr.Next(time.Date(
|
||||
2000, 1, 1,
|
||||
20, 10, 5,
|
||||
0, time.UTC,
|
||||
)))
|
||||
|
||||
// Output:
|
||||
// 2000-01-01 21:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleCron() {
|
||||
d := timer.NewDispatcher(10)
|
||||
|
||||
// cron expr
|
||||
cronExpr, err := timer.NewCronExpr("* * * * * *")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// cron
|
||||
var c *timer.Cron
|
||||
c = d.CronFunc(cronExpr, func() {
|
||||
fmt.Println("My name is Leaf")
|
||||
c.Stop()
|
||||
})
|
||||
|
||||
// dispatch
|
||||
(<-d.ChanTimer).Cb()
|
||||
|
||||
// Output:
|
||||
// My name is Leaf
|
||||
}
|
||||
134
util/timer/timer.go
Normal file
134
util/timer/timer.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package timer
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// one dispatcher per goroutine (goroutine not safe)
|
||||
type Dispatcher struct {
|
||||
ChanTimer chan *Timer
|
||||
}
|
||||
|
||||
func NewDispatcher(l int) *Dispatcher {
|
||||
disp := new(Dispatcher)
|
||||
disp.ChanTimer = make(chan *Timer, l)
|
||||
return disp
|
||||
}
|
||||
|
||||
// Timer
|
||||
type Timer struct {
|
||||
t *time.Timer
|
||||
cb func()
|
||||
cbex func(*Timer)
|
||||
}
|
||||
|
||||
func (t *Timer) Stop() {
|
||||
t.t.Stop()
|
||||
t.cb = nil
|
||||
}
|
||||
|
||||
func (t *Timer) Cb() {
|
||||
/*
|
||||
defer func() {
|
||||
t.cb = nil
|
||||
if r := recover(); r != nil {
|
||||
if conf.LenStackBuf > 0 {
|
||||
buf := make([]byte, conf.LenStackBuf)
|
||||
l := runtime.Stack(buf, false)
|
||||
log.Error("%v: %s", r, buf[:l])
|
||||
} else {
|
||||
log.Error("%v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
*/
|
||||
|
||||
if t.cbex!=nil {
|
||||
t.cbex(t)
|
||||
}else{
|
||||
t.cb()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer {
|
||||
t := new(Timer)
|
||||
t.cb = cb
|
||||
t.t = time.AfterFunc(d, func() {
|
||||
disp.ChanTimer <- t
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
func (disp *Dispatcher) AfterFuncEx(d time.Duration, cbex func(timer *Timer)) *Timer {
|
||||
t := new(Timer)
|
||||
t.cbex = cbex
|
||||
t.t = time.AfterFunc(d, func() {
|
||||
disp.ChanTimer <- t
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
// Cron
|
||||
type Cron struct {
|
||||
t *Timer
|
||||
}
|
||||
|
||||
func (c *Cron) Stop() {
|
||||
if c.t != nil {
|
||||
c.t.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron {
|
||||
c := new(Cron)
|
||||
|
||||
now := time.Now()
|
||||
nextTime := cronExpr.Next(now)
|
||||
if nextTime.IsZero() {
|
||||
return c
|
||||
}
|
||||
|
||||
// callback
|
||||
var cb func()
|
||||
cb = func() {
|
||||
defer _cb()
|
||||
|
||||
now := time.Now()
|
||||
nextTime := cronExpr.Next(now)
|
||||
if nextTime.IsZero() {
|
||||
return
|
||||
}
|
||||
c.t = disp.AfterFunc(nextTime.Sub(now), cb)
|
||||
}
|
||||
|
||||
c.t = disp.AfterFunc(nextTime.Sub(now), cb)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
func (disp *Dispatcher) CronFuncEx(cronExpr *CronExpr, _cb func(*Cron)) *Cron {
|
||||
c := new(Cron)
|
||||
|
||||
now := time.Now()
|
||||
nextTime := cronExpr.Next(now)
|
||||
if nextTime.IsZero() {
|
||||
return c
|
||||
}
|
||||
|
||||
// callback
|
||||
var cb func()
|
||||
cb = func() {
|
||||
defer _cb(c)
|
||||
|
||||
now := time.Now()
|
||||
nextTime := cronExpr.Next(now)
|
||||
if nextTime.IsZero() {
|
||||
return
|
||||
}
|
||||
c.t = disp.AfterFunc(nextTime.Sub(now), cb)
|
||||
}
|
||||
|
||||
c.t = disp.AfterFunc(nextTime.Sub(now), cb)
|
||||
return c
|
||||
}
|
||||
Reference in New Issue
Block a user