impl schedule with cron, dur, and const ts
parent
a63c7bfbb2
commit
28baac7dc6
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue