Skip to content

Instantly share code, notes, and snippets.

@mindon
Last active September 4, 2024 06:10
Show Gist options
  • Select an option

  • Save mindon/b4588f0be5aa1d8dc43ae52f69aa5649 to your computer and use it in GitHub Desktop.

Select an option

Save mindon/b4588f0be5aa1d8dc43ae52f69aa5649 to your computer and use it in GitHub Desktop.
Schedule daily tasks with golang, without any third-party dependence
// 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