Last active
September 4, 2024 06:10
-
-
Save mindon/b4588f0be5aa1d8dc43ae52f69aa5649 to your computer and use it in GitHub Desktop.
Schedule daily tasks with golang, without any third-party dependence
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Schedule daily tasks at moment or every dur from at to another time point of the day | |
| // schedule.go | |
| package main | |
| import ( | |
| "fmt" | |
| "log" | |
| "strings" | |
| "time" | |
| ) | |
| // TaskAt to run task at time, or run every duration until a timepoint, daily | |
| type TaskAt struct { | |
| Name string | |
| At string | |
| Do func() error | |
| Every string | |
| at time.Time | |
| fin time.Time | |
| every time.Duration | |
| parsed bool | |
| } | |
| // Parse task configuration | |
| func (t *TaskAt) Parse() (err error) { | |
| if len(t.Name) == 0 { | |
| t.Name = t.At | |
| } | |
| if strings.Count(t.At, ":") == 1 { | |
| t.At += ":00" | |
| } | |
| t.at, err = time.Parse("15:04:05", t.At) | |
| if err != nil { | |
| return | |
| } | |
| if len(t.Every) > 0 { | |
| durFin := strings.Split(t.Every, ",") | |
| if len(durFin) < 2 { | |
| err = fmt.Errorf("Every invalid") | |
| return | |
| } | |
| if strings.Count(durFin[1], ":") == 1 { | |
| durFin[1] += ":00" | |
| } | |
| t.fin, err = time.Parse("15:04:05", durFin[1]) | |
| if err != nil { | |
| return | |
| } | |
| t.every, err = time.ParseDuration(durFin[0]) | |
| if err != nil { | |
| return | |
| } | |
| if t.fin.Before(t.at) || t.every <= 0 { | |
| err = fmt.Errorf("Every invalid") | |
| return | |
| } | |
| } | |
| return | |
| } | |
| // Run task | |
| func (t *TaskAt) Run(now time.Time) (err error) { | |
| defer func() { | |
| if err != nil { | |
| log.Println(t.Name, err) | |
| } | |
| }() | |
| // only parse once | |
| if !t.parsed { | |
| err = t.Parse() | |
| if err != nil { | |
| return | |
| } | |
| t.parsed = true | |
| } | |
| next := time.Date(now.Year(), now.Month(), now.Day(), | |
| t.at.Hour(), t.at.Minute(), t.at.Second(), 0, now.Location()) | |
| tolerance := 0 * time.Second | |
| if t.at.Second() == 0 { | |
| tolerance = -5 * time.Second | |
| } | |
| if next.Sub(now) <= tolerance { | |
| next = next.AddDate(0, 0, 1) | |
| } | |
| time.Sleep(next.Sub(now)) | |
| if t.every <= 0 || t.fin.Before(t.at) { | |
| return t.Do() | |
| } | |
| done := make(chan bool) | |
| ticker := time.NewTicker(t.every) | |
| time.AfterFunc(t.fin.Sub(t.at), func() { | |
| done <- true | |
| }) | |
| err = t.Do() | |
| if err != nil { | |
| ticker.Stop() | |
| return | |
| } | |
| for { | |
| select { | |
| case <-done: | |
| ticker.Stop() | |
| return | |
| case <-ticker.C: | |
| err = t.Do() | |
| if err != nil { | |
| done <- true | |
| } | |
| } | |
| } | |
| } | |
| // Daily tasks run at moments | |
| func Daily(tasks []TaskAt) { | |
| for { | |
| now := time.Now() | |
| for _, task := range tasks { | |
| go task.Run(now) | |
| } | |
| // 24 hours | |
| time.Sleep(24 * time.Hour) | |
| } | |
| } | |
| // demo usage | |
| func main() { | |
| now := time.Now().Add(1 * time.Second) | |
| Daily([]TaskAt{ | |
| { | |
| At: now.Format("15:04:05"), | |
| Do: func() error { | |
| log.Printf("hello, %v\n", time.Now()) | |
| return nil | |
| }, | |
| }, | |
| { | |
| At: now.Add(5 * time.Second).Format("15:04:05"), | |
| Do: func() error { | |
| log.Printf("world, %v\n", time.Now()) | |
| return nil | |
| }, | |
| Every: fmt.Sprintf("2s,%s", now.Add(15*time.Second).Format("15:04:05")), | |
| }, | |
| }) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment