You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
366 lines
9.1 KiB
366 lines
9.1 KiB
package gocron
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrTimeFormat = errors.New("time format error")
|
|
ErrParamsNotAdapted = errors.New("the number of params is not adapted")
|
|
ErrNotAFunction = errors.New("only functions can be schedule into the job queue")
|
|
ErrPeriodNotSpecified = errors.New("unspecified job period")
|
|
ErrParameterCannotBeNil = errors.New("nil paramaters cannot be used with reflection")
|
|
)
|
|
|
|
// Job struct keeping information about job
|
|
type Job struct {
|
|
interval uint64 // pause interval * unit between runs
|
|
jobFunc string // the job jobFunc to run, func[jobFunc]
|
|
unit timeUnit // time units, ,e.g. 'minutes', 'hours'...
|
|
atTime time.Duration // optional time at which this job runs
|
|
err error // error related to job
|
|
loc *time.Location // optional timezone that the atTime is in
|
|
lastRun time.Time // datetime of last run
|
|
nextRun time.Time // datetime of next run
|
|
startDay time.Weekday // Specific day of the week to start on
|
|
funcs map[string]interface{} // Map for the function task store
|
|
fparams map[string][]interface{} // Map for function and params of function
|
|
lock bool // lock the job from running at same time form multiple instances
|
|
tags []string // allow the user to tag jobs with certain labels
|
|
}
|
|
|
|
// NewJob creates a new job with the time interval.
|
|
func NewJob(interval uint64) *Job {
|
|
return &Job{
|
|
interval: interval,
|
|
loc: loc,
|
|
lastRun: time.Unix(0, 0),
|
|
nextRun: time.Unix(0, 0),
|
|
startDay: time.Sunday,
|
|
funcs: make(map[string]interface{}),
|
|
fparams: make(map[string][]interface{}),
|
|
tags: []string{},
|
|
}
|
|
}
|
|
|
|
// True if the job should be run now
|
|
func (j *Job) shouldRun() bool {
|
|
return time.Now().Unix() >= j.nextRun.Unix()
|
|
}
|
|
|
|
//Run the job and immediately reschedule it
|
|
func (j *Job) run() ([]reflect.Value, error) {
|
|
if j.lock {
|
|
if locker == nil {
|
|
return nil, fmt.Errorf("trying to lock %s with nil locker", j.jobFunc)
|
|
}
|
|
key := getFunctionKey(j.jobFunc)
|
|
|
|
locker.Lock(key)
|
|
defer locker.Unlock(key)
|
|
}
|
|
result, err := callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Err should be checked to ensure an error didn't occur creating the job
|
|
func (j *Job) Err() error {
|
|
return j.err
|
|
}
|
|
|
|
// Do specifies the jobFunc that should be called every time the job runs
|
|
func (j *Job) Do(jobFun interface{}, params ...interface{}) error {
|
|
if j.err != nil {
|
|
return j.err
|
|
}
|
|
|
|
typ := reflect.TypeOf(jobFun)
|
|
if typ.Kind() != reflect.Func {
|
|
return ErrNotAFunction
|
|
}
|
|
fname := getFunctionName(jobFun)
|
|
j.funcs[fname] = jobFun
|
|
j.fparams[fname] = params
|
|
j.jobFunc = fname
|
|
|
|
now := time.Now().In(j.loc)
|
|
if !j.nextRun.After(now) {
|
|
j.scheduleNextRun()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DoSafely does the same thing as Do, but logs unexpected panics, instead of unwinding them up the chain
|
|
// Deprecated: DoSafely exists due to historical compatibility and will be removed soon. Use Do instead
|
|
func (j *Job) DoSafely(jobFun interface{}, params ...interface{}) error {
|
|
recoveryWrapperFunc := func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Printf("Internal panic occurred: %s", r)
|
|
}
|
|
}()
|
|
|
|
_, _ = callJobFuncWithParams(jobFun, params)
|
|
}
|
|
|
|
return j.Do(recoveryWrapperFunc)
|
|
}
|
|
|
|
// At schedules job at specific time of day
|
|
// s.Every(1).Day().At("10:30:01").Do(task)
|
|
// s.Every(1).Monday().At("10:30:01").Do(task)
|
|
func (j *Job) At(t string) *Job {
|
|
hour, min, sec, err := formatTime(t)
|
|
if err != nil {
|
|
j.err = ErrTimeFormat
|
|
return j
|
|
}
|
|
// save atTime start as duration from midnight
|
|
j.atTime = time.Duration(hour)*time.Hour + time.Duration(min)*time.Minute + time.Duration(sec)*time.Second
|
|
return j
|
|
}
|
|
|
|
// GetAt returns the specific time of day the job will run at
|
|
// s.Every(1).Day().At("10:30").GetAt() == "10:30"
|
|
func (j *Job) GetAt() string {
|
|
return fmt.Sprintf("%d:%d", j.atTime/time.Hour, (j.atTime%time.Hour)/time.Minute)
|
|
}
|
|
|
|
// Loc sets the location for which to interpret "At"
|
|
// s.Every(1).Day().At("10:30").Loc(time.UTC).Do(task)
|
|
func (j *Job) Loc(loc *time.Location) *Job {
|
|
j.loc = loc
|
|
return j
|
|
}
|
|
|
|
// Tag allows you to add labels to a job
|
|
// they don't impact the functionality of the job.
|
|
func (j *Job) Tag(t string, others ...string) {
|
|
j.tags = append(j.tags, t)
|
|
for _, tag := range others {
|
|
j.tags = append(j.tags, tag)
|
|
}
|
|
}
|
|
|
|
// Untag removes a tag from a job
|
|
func (j *Job) Untag(t string) {
|
|
newTags := []string{}
|
|
for _, tag := range j.tags {
|
|
if t != tag {
|
|
newTags = append(newTags, tag)
|
|
}
|
|
}
|
|
|
|
j.tags = newTags
|
|
}
|
|
|
|
// Tags returns the tags attached to the job
|
|
func (j *Job) Tags() []string {
|
|
return j.tags
|
|
}
|
|
|
|
func (j *Job) periodDuration() (time.Duration, error) {
|
|
interval := time.Duration(j.interval)
|
|
var periodDuration time.Duration
|
|
|
|
switch j.unit {
|
|
case seconds:
|
|
periodDuration = interval * time.Second
|
|
case minutes:
|
|
periodDuration = interval * time.Minute
|
|
case hours:
|
|
periodDuration = interval * time.Hour
|
|
case days:
|
|
periodDuration = interval * time.Hour * 24
|
|
case weeks:
|
|
periodDuration = interval * time.Hour * 24 * 7
|
|
default:
|
|
return 0, ErrPeriodNotSpecified
|
|
}
|
|
return periodDuration, nil
|
|
}
|
|
|
|
// roundToMidnight truncate time to midnight
|
|
func (j *Job) roundToMidnight(t time.Time) time.Time {
|
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, j.loc)
|
|
}
|
|
|
|
// scheduleNextRun Compute the instant when this job should run next
|
|
func (j *Job) scheduleNextRun() error {
|
|
now := time.Now()
|
|
if j.lastRun == time.Unix(0, 0) {
|
|
j.lastRun = now
|
|
}
|
|
|
|
periodDuration, err := j.periodDuration()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch j.unit {
|
|
case seconds, minutes, hours:
|
|
j.nextRun = j.lastRun.Add(periodDuration)
|
|
case days:
|
|
j.nextRun = j.roundToMidnight(j.lastRun)
|
|
j.nextRun = j.nextRun.Add(j.atTime)
|
|
case weeks:
|
|
j.nextRun = j.roundToMidnight(j.lastRun)
|
|
dayDiff := int(j.startDay)
|
|
dayDiff -= int(j.nextRun.Weekday())
|
|
if dayDiff != 0 {
|
|
j.nextRun = j.nextRun.Add(time.Duration(dayDiff) * 24 * time.Hour)
|
|
}
|
|
j.nextRun = j.nextRun.Add(j.atTime)
|
|
}
|
|
|
|
// advance to next possible schedule
|
|
for j.nextRun.Before(now) || j.nextRun.Before(j.lastRun) {
|
|
j.nextRun = j.nextRun.Add(periodDuration)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NextScheduledTime returns the time of when this job is to run next
|
|
func (j *Job) NextScheduledTime() time.Time {
|
|
return j.nextRun
|
|
}
|
|
|
|
// set the job's unit with seconds,minutes,hours...
|
|
func (j *Job) mustInterval(i uint64) error {
|
|
if j.interval != i {
|
|
return fmt.Errorf("interval must be %d", i)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// From schedules the next run of the job
|
|
func (j *Job) From(t *time.Time) *Job {
|
|
j.nextRun = *t
|
|
return j
|
|
}
|
|
|
|
// setUnit sets unit type
|
|
func (j *Job) setUnit(unit timeUnit) *Job {
|
|
j.unit = unit
|
|
return j
|
|
}
|
|
|
|
// Seconds set the unit with seconds
|
|
func (j *Job) Seconds() *Job {
|
|
return j.setUnit(seconds)
|
|
}
|
|
|
|
// Minutes set the unit with minute
|
|
func (j *Job) Minutes() *Job {
|
|
return j.setUnit(minutes)
|
|
}
|
|
|
|
// Hours set the unit with hours
|
|
func (j *Job) Hours() *Job {
|
|
return j.setUnit(hours)
|
|
}
|
|
|
|
// Days set the job's unit with days
|
|
func (j *Job) Days() *Job {
|
|
return j.setUnit(days)
|
|
}
|
|
|
|
// Weeks sets the units as weeks
|
|
func (j *Job) Weeks() *Job {
|
|
return j.setUnit(weeks)
|
|
}
|
|
|
|
// Second sets the unit with second
|
|
func (j *Job) Second() *Job {
|
|
j.mustInterval(1)
|
|
return j.Seconds()
|
|
}
|
|
|
|
// Minute sets the unit with minute, which interval is 1
|
|
func (j *Job) Minute() *Job {
|
|
j.mustInterval(1)
|
|
return j.Minutes()
|
|
}
|
|
|
|
// Hour sets the unit with hour, which interval is 1
|
|
func (j *Job) Hour() *Job {
|
|
j.mustInterval(1)
|
|
return j.Hours()
|
|
}
|
|
|
|
// Day sets the job's unit with day, which interval is 1
|
|
func (j *Job) Day() *Job {
|
|
j.mustInterval(1)
|
|
return j.Days()
|
|
}
|
|
|
|
// Week sets the job's unit with week, which interval is 1
|
|
func (j *Job) Week() *Job {
|
|
j.mustInterval(1)
|
|
return j.Weeks()
|
|
}
|
|
|
|
// Weekday start job on specific Weekday
|
|
func (j *Job) Weekday(startDay time.Weekday) *Job {
|
|
j.mustInterval(1)
|
|
j.startDay = startDay
|
|
return j.Weeks()
|
|
}
|
|
|
|
// GetWeekday returns which day of the week the job will run on
|
|
// This should only be used when .Weekday(...) was called on the job.
|
|
func (j *Job) GetWeekday() time.Weekday {
|
|
return j.startDay
|
|
}
|
|
|
|
// Monday set the start day with Monday
|
|
// - s.Every(1).Monday().Do(task)
|
|
func (j *Job) Monday() (job *Job) {
|
|
return j.Weekday(time.Monday)
|
|
}
|
|
|
|
// Tuesday sets the job start day Tuesday
|
|
func (j *Job) Tuesday() *Job {
|
|
return j.Weekday(time.Tuesday)
|
|
}
|
|
|
|
// Wednesday sets the job start day Wednesday
|
|
func (j *Job) Wednesday() *Job {
|
|
return j.Weekday(time.Wednesday)
|
|
}
|
|
|
|
// Thursday sets the job start day Thursday
|
|
func (j *Job) Thursday() *Job {
|
|
return j.Weekday(time.Thursday)
|
|
}
|
|
|
|
// Friday sets the job start day Friday
|
|
func (j *Job) Friday() *Job {
|
|
return j.Weekday(time.Friday)
|
|
}
|
|
|
|
// Saturday sets the job start day Saturday
|
|
func (j *Job) Saturday() *Job {
|
|
return j.Weekday(time.Saturday)
|
|
}
|
|
|
|
// Sunday sets the job start day Sunday
|
|
func (j *Job) Sunday() *Job {
|
|
return j.Weekday(time.Sunday)
|
|
}
|
|
|
|
// Lock prevents job to run from multiple instances of gocron
|
|
func (j *Job) Lock() *Job {
|
|
j.lock = true
|
|
return j
|
|
}
|