From 28baac7dc64342d0082d90eba6582d33fe6b5a63 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Fri, 31 Dec 2021 15:56:17 -0500 Subject: [PATCH] impl schedule with cron, dur, and const ts --- pttodo/schedule.go | 79 ++++++++++++++++++++++++++++++++++++++++- pttodo/schedule_test.go | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/pttodo/schedule.go b/pttodo/schedule.go index 6c3de04..68cdd60 100644 --- a/pttodo/schedule.go +++ b/pttodo/schedule.go @@ -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 +} diff --git a/pttodo/schedule_test.go b/pttodo/schedule_test.go index 846f853..8e96f4b 100644 --- a/pttodo/schedule_test.go +++ b/pttodo/schedule_test.go @@ -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) + } + }) + } +}