impl schedule with cron, dur, and const ts

master
Bel LaPointe 2021-12-31 15:56:17 -05:00
parent a63c7bfbb2
commit 28baac7dc6
2 changed files with 142 additions and 1 deletions

View File

@ -1,6 +1,14 @@
package pttodo
import "encoding/json"
import (
"encoding/json"
"math"
"regexp"
"strconv"
"time"
cron "github.com/robfig/cron/v3"
)
type Schedule string
@ -11,3 +19,72 @@ func (schedule Schedule) MarshalJSON() ([]byte, error) {
func (schedule *Schedule) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, (*string)(schedule))
}
func (schedule Schedule) Next(t time.Time) (time.Time, error) {
scheduler := schedulerFactory(string(schedule))
return scheduler.next(t)
}
type scheduler interface {
next(time.Time) (time.Time, error)
}
func schedulerFactory(s string) scheduler {
if s == "" {
return scheduleNever{}
} else if scheduleDurationPattern.MatchString(s) {
return scheduleDuration(s)
} else if scheduleDuePattern.MatchString(s) {
n, _ := strconv.Atoi(s)
return scheduleDue(n)
}
return scheduleCron(s)
}
// * * * * *
type scheduleCron string
func (c scheduleCron) next(t time.Time) (time.Time, error) {
schedule, err := cron.ParseStandard(string(c))
if err != nil {
return time.Time{}, err
}
return schedule.Next(t), nil
}
// * * * * *
type scheduleNever struct{}
var never = time.Date(
math.MaxInt32,
1,
1,
1,
1,
1,
1,
time.UTC,
)
func (cron scheduleNever) next(time.Time) (time.Time, error) {
return never, nil
}
// 4h5m
type scheduleDuration string
var scheduleDurationPattern = regexp.MustCompile(`^([0-9]+[a-z])+$`)
func (dur scheduleDuration) next(t time.Time) (time.Time, error) {
d, err := time.ParseDuration(string(dur))
return t.Add(d), err
}
// 123
type scheduleDue int64
var scheduleDuePattern = regexp.MustCompile(`^[0-9]+$`)
func (due scheduleDue) next(time.Time) (time.Time, error) {
return time.Unix(int64(due), 0), nil
}

View File

@ -3,7 +3,9 @@ package pttodo
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"time"
)
func TestJSONSchedule(t *testing.T) {
@ -45,3 +47,65 @@ func TestJSONSchedule(t *testing.T) {
})
}
}
func TestSchedulerFactory(t *testing.T) {
start := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC)
cases := map[string]struct {
input string
want interface{}
next time.Time
}{
"long dur": {
input: `2h1m`,
want: scheduleDuration("2h1m"),
next: start.Add(time.Hour * 2).Add(time.Minute),
},
"simple dur": {
input: `1m`,
want: scheduleDuration("1m"),
next: start.Add(time.Minute),
},
"little ts": {
input: `1`,
want: scheduleDue(1),
next: time.Unix(1, 0),
},
"zero ts": {
input: `0`,
want: scheduleDue(0),
next: time.Unix(0, 0),
},
"never": {
input: ``,
want: scheduleNever{},
next: never,
},
"always cron": {
input: `* * * * *`,
want: scheduleCron(`* * * * *`),
next: start.Add(time.Duration(-1*start.Nanosecond() + -1*start.Second())).Add(time.Minute),
},
"fifth minute cron": {
input: `5 * * * *`,
want: scheduleCron(`5 * * * *`),
next: start.Add(time.Duration(-1*start.Nanosecond() + -1*start.Minute() + -1*start.Second())).Add(5 * time.Minute),
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
got := schedulerFactory(c.input)
if fmt.Sprintf("%T", c.want) != fmt.Sprintf("%T", got) {
t.Fatalf("want type %T, got %T", c.want, got)
}
if fmt.Sprint(c.want) != fmt.Sprint(got) {
t.Fatalf("want %+v, got %+v", c.want, got)
}
next, _ := got.next(start)
if next.Sub(c.next) > time.Second {
t.Fatalf("want next %+v, got %+v", c.next, next)
}
})
}
}