impl schedule with cron, dur, and const ts
parent
a63c7bfbb2
commit
28baac7dc6
|
|
@ -1,6 +1,14 @@
|
||||||
package pttodo
|
package pttodo
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
cron "github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
type Schedule string
|
type Schedule string
|
||||||
|
|
||||||
|
|
@ -11,3 +19,72 @@ func (schedule Schedule) MarshalJSON() ([]byte, error) {
|
||||||
func (schedule *Schedule) UnmarshalJSON(b []byte) error {
|
func (schedule *Schedule) UnmarshalJSON(b []byte) error {
|
||||||
return json.Unmarshal(b, (*string)(schedule))
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJSONSchedule(t *testing.T) {
|
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