Skip to content

Commit

Permalink
scheduler: Use Next instead of First (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunshineplan committed May 23, 2024
1 parent 4b2af58 commit 51ee424
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 90 deletions.
65 changes: 36 additions & 29 deletions scheduler/clock.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,45 +142,52 @@ func (c Clock) IsMatched(t time.Time) bool {
(!c.sec || c.Clock.Second() == sec)
}

func (c Clock) First(t time.Time) time.Duration {
u, nc := AtClock(t.Clock()), new(Clock)
func (c Clock) Next(t time.Time) (next time.Time) {
year, month, day := t.Date()
var hour, min, sec int
if c.sec {
nc.Second(c.Clock.Second())
sec = c.Clock.Second()
} else {
nc.Second(u.Clock.Second())
sec = t.Second()
}
if c.min {
nc.Minute(c.Clock.Minute())
min = c.Clock.Minute()
} else {
nc.Minute(u.Clock.Minute())
min = t.Minute()
}
if c.hour {
nc.Hour(c.Clock.Hour())
hour = c.Clock.Hour()
} else {
nc.Hour(u.Clock.Hour())
hour = t.Hour()
}

if nc.Equal(u.Clock) {
return 0
}
if !c.sec {
nc.Second(0)
}
if nc.Before(u.Clock) {
switch next = time.Date(year, month, day, hour, min, sec, 0, t.Location()); t.Truncate(time.Second).Compare(next) {
case 1:
if !c.sec {
next = next.Add(-time.Duration(sec) * time.Second)
}
if !c.min {
if nc.Clock.Hour() == u.Clock.Hour() {
return nc.Add(time.Minute).Since(u.Clock)
if t.Hour() == hour {
if t := next.Add(time.Minute); t.Hour() == hour {
return t
}
}
nc.Minute(0)
next = next.Add(-time.Duration(min) * time.Minute)
}
if !c.hour {
return nc.Add(time.Hour).Since(u.Clock)
return next.Add(time.Hour)
}
return next.AddDate(0, 0, 1)
case -1:
if !c.sec {
next = next.Add(-time.Duration(sec) * time.Second)
}
if !c.min && t.Hour() != hour {
next = next.Add(-time.Duration(min) * time.Minute)
}
return nc.Add(24 * time.Hour).Since(u.Clock)
} else if !c.min && nc.Clock.Hour() != u.Clock.Hour() {
nc.Minute(0)
return
default:
return t
}
return nc.Since(u.Clock)
}

func (c Clock) TickerDuration() time.Duration {
Expand Down Expand Up @@ -232,17 +239,17 @@ func (s clockSched) IsMatched(t time.Time) bool {
return (start.Equal(tc) || start.Before(tc) && end.After(tc) || end.Equal(tc)) && tc.Since(start.Clock)%s.d == 0
}

func (s clockSched) First(t time.Time) time.Duration {
func (s clockSched) Next(t time.Time) time.Time {
if s.IsMatched(t) {
return 0
return t
}
start, end, tc := s.start.Clock, s.end.Clock, AtClock(t.Clock()).Clock
start, end := s.start.Clock, s.end.Clock
for c := AtClock(t.Clock()); c.Compare(start) != -1 && c.Compare(end) != 1; c.Clock = c.Add(time.Second) {
if s.IsMatched(c.Time()) {
return c.Since(tc)
return time.Date(t.Year(), t.Month(), t.Day(), c.Clock.Hour(), c.Clock.Minute(), c.Clock.Second(), 0, t.Location())
}
}
return s.start.First(t)
return s.start.Next(t)
}

func (s clockSched) TickerDuration() time.Duration {
Expand Down
66 changes: 36 additions & 30 deletions scheduler/clock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,38 @@ func TestClock(t *testing.T) {
if res := s.TickerDuration(); res != 24*time.Hour {
t.Errorf("expected 24h; got %v", res)
}
if res := s.First(AtClock(6, 0, 0).Time()); res != time.Hour {
t.Errorf("expected 1h; got %v", res)
if res := s.Next(AtClock(6, 0, 0).Time()).Format("15:04:05"); res != "07:00:00" {
t.Errorf("expected 07:00:00; got %q", res)
}
}

func TestClockFirst(t *testing.T) {
func TestClockNext(t *testing.T) {
ct := AtClock(12, 30, 30).Time()
for _, testcase := range []struct {
clock *Clock
time string
expected time.Duration
}{
{AtClock(0, 0, 0), 11*time.Hour + 29*time.Minute + 30*time.Second}, // 00:00:00
{AtClock(-1, -1, -1), 0}, // 12:30:30
{AtClock(-1, -1, 30), 0}, // 12:30:30
{AtClock(-1, 30, -1), 0}, // 12:30:30
{AtClock(12, -1, -1), 0}, // 12:30:30
{AtClock(12, -1, 30), 0}, // 12:30:30
{AtClock(-1, -1, 15), 45 * time.Second}, // 12:31:15
{AtClock(-1, -1, 45), 15 * time.Second}, // 12:30:45
{AtClock(-1, 15, -1), 44*time.Minute + 30*time.Second}, // 13:15:00
{AtClock(-1, 45, -1), 14*time.Minute + 30*time.Second}, // 12:45:00
{AtClock(6, -1, -1), 17*time.Hour + 29*time.Minute + 30*time.Second}, // 06:00:00+1
{AtClock(18, -1, -1), 5*time.Hour + 29*time.Minute + 30*time.Second}, // 18:00:00
{AtClock(6, -1, 15), 17*time.Hour + 29*time.Minute + 45*time.Second}, // 06:00:15+1
{AtClock(18, -1, 45), 5*time.Hour + 30*time.Minute + 15*time.Second}, // 18:00:45
{AtClock(0, 0, 0), "00:00:00", 11*time.Hour + 29*time.Minute + 30*time.Second},
{AtClock(-1, -1, -1), "12:30:30", 0},
{AtClock(-1, -1, 30), "12:30:30", 0},
{AtClock(-1, 30, -1), "12:30:30", 0},
{AtClock(12, -1, -1), "12:30:30", 0},
{AtClock(12, -1, 30), "12:30:30", 0},
{AtClock(-1, -1, 15), "12:31:15", 45 * time.Second},
{AtClock(-1, -1, 45), "12:30:45", 15 * time.Second},
{AtClock(-1, 15, -1), "13:15:00", 44*time.Minute + 30*time.Second},
{AtClock(-1, 45, -1), "12:45:00", 14*time.Minute + 30*time.Second},
{AtClock(6, -1, -1), "06:00:00", 17*time.Hour + 29*time.Minute + 30*time.Second}, // +1
{AtClock(18, -1, -1), "18:00:00", 5*time.Hour + 29*time.Minute + 30*time.Second},
{AtClock(6, -1, 15), "06:00:15", 17*time.Hour + 29*time.Minute + 45*time.Second}, // +1
{AtClock(18, -1, 45), "18:00:45", 5*time.Hour + 30*time.Minute + 15*time.Second},
} {
if res := testcase.clock.First(AtClock(12, 30, 30).Time()); res != testcase.expected {
next := testcase.clock.Next(ct)
if res := next.Format("15:04:05"); res != testcase.time {
t.Errorf("%s expected %q; got %q", testcase.clock, testcase.time, res)
}
if res := next.Sub(ct); res != testcase.expected {
t.Errorf("%s expected %v; got %v", testcase.clock, testcase.expected, res)
}
}
Expand Down Expand Up @@ -74,28 +80,28 @@ func TestClockSchedule(t *testing.T) {
s := ClockSchedule(AtHour(9).Minute(30), AtHour(15), 2*time.Minute)
for _, testcase := range []struct {
clock *Clock
duration time.Duration
time string
expected bool
}{
{AtClock(9, 30, 0), 0, true},
{AtClock(15, 0, 0), 0, true},
{AtClock(13, 0, 0), 0, true},
{ClockFromString("9:29"), time.Minute, false},
{ClockFromString("9:31"), time.Minute, false},
{ClockFromString("16:00:00"), 17*time.Hour + 30*time.Minute, false},
{AtClock(9, 30, 0), "09:30:00", true},
{AtClock(15, 0, 0), "15:00:00", true},
{AtClock(13, 0, 0), "13:00:00", true},
{ClockFromString("9:29"), "09:30:00", false},
{ClockFromString("9:31"), "09:32:00", false},
{ClockFromString("16:00:00"), "09:30:00", false},
} {
if res := s.First(testcase.clock.Time()); res != testcase.duration {
t.Errorf("%s expected %v; got %v", testcase.clock, testcase.duration, res)
if res := s.Next(testcase.clock.Time()).Format("15:04:05"); res != testcase.time {
t.Errorf("%s expected %q; got %q", testcase.clock, testcase.time, res)
}
if res := s.IsMatched(testcase.clock.Time()); res != testcase.expected {
t.Errorf("%s expected %v; got %v", testcase.clock, testcase.expected, res)
}
}
}

func TestClockScheduleFirst(t *testing.T) {
func TestClockScheduleNext(t *testing.T) {
s := ClockSchedule(ClockFromString("6:00"), ClockFromString("22:00"), time.Hour)
if res := s.First(AtClock(21, 59, 0).Time()); res != time.Minute {
t.Errorf("expected 1m; got %v", res)
if res := s.Next(AtClock(21, 59, 0).Time()).Format("15:04:05"); res != "22:00:00" {
t.Errorf("expected 22:00:00; got %q", res)
}
}
20 changes: 10 additions & 10 deletions scheduler/complex.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var (

type complexSched interface {
IsMatched(time.Time) bool
First(time.Time) time.Duration
Next(time.Time) time.Time
TickerDuration() time.Duration
String() string

Expand Down Expand Up @@ -60,16 +60,15 @@ func (s multiSched) IsMatched(t time.Time) bool {
return false
}

func (s multiSched) First(t time.Time) time.Duration {
var res time.Duration
func (s multiSched) Next(t time.Time) (next time.Time) {
for i, sched := range s {
if i == 0 {
res = sched.First(t)
} else if d := sched.First(t); d < res {
res = d
next = sched.Next(t)
} else if t := sched.Next(t); t.Before(next) {
next = t
}
}
return res
return
}

func (s multiSched) TickerDuration() time.Duration {
Expand Down Expand Up @@ -140,11 +139,12 @@ func (s condSched) IsMatched(t time.Time) bool {
return true
}

func (s condSched) First(t time.Time) time.Duration {
func (s condSched) Next(t time.Time) time.Time {
if len(s) == 1 {
return s[0].First(t)
return s[0].Next(t)
}
return first(t, s.TickerDuration())
d := s.TickerDuration()
return t.Add(d).Truncate(d)
}

func (s condSched) TickerDuration() time.Duration {
Expand Down
8 changes: 4 additions & 4 deletions scheduler/complex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ func TestMultiSchedule(t *testing.T) {
if d := s.TickerDuration(); d != time.Minute {
t.Fatalf("expected 1m: got %s", d)
}
if d := s.First(time.Date(2006, 1, 2, 0, 0, 0, 0, time.Local)); d != 5*time.Second {
t.Fatalf("expected 5s: got %s", d)
if res := s.Next(time.Time{}).Format("15:04:05"); res != "00:00:05" {
t.Fatalf("expected 00:00:05: got %q", res)
}
for _, testcase := range []struct {
clock *Clock
Expand Down Expand Up @@ -48,8 +48,8 @@ func TestConditionSchedule(t *testing.T) {
if d := s.TickerDuration(); d != time.Second {
t.Fatalf("expected 1s: got %s", d)
}
if d := s.First(time.Date(2006, 1, 2, 3, 4, 5, 0, time.Local)); d != time.Second {
t.Fatalf("expected 1s: got %s", d)
if res := s.Next(time.Time{}).Format("15:04:05"); res != "00:00:01" {
t.Fatalf("expected 00:00:01: got %q", res)
}
for _, testcase := range []struct {
clock *Clock
Expand Down
4 changes: 0 additions & 4 deletions scheduler/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,3 @@ func gcd(a, b time.Duration) time.Duration {
}
return a
}

func first(t time.Time, d time.Duration) time.Duration {
return t.Add(d).Truncate(d).Sub(t)
}
20 changes: 10 additions & 10 deletions scheduler/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

type Schedule interface {
IsMatched(time.Time) bool
First(time.Time) time.Duration
Next(time.Time) time.Time
TickerDuration() time.Duration
String() string
}
Expand Down Expand Up @@ -77,11 +77,11 @@ func (s sched) IsMatched(t time.Time) bool {
return false
}

func (s sched) First(t time.Time) time.Duration {
func (s sched) Next(t time.Time) time.Time {
if s.clock == nil {
s.clock = &Clock{}
}
return s.clock.First(t)
return s.clock.Next(t)
}

func (s sched) TickerDuration() time.Duration {
Expand Down Expand Up @@ -138,11 +138,11 @@ func (s weekSched) IsMatched(t time.Time) bool {
return false
}

func (s weekSched) First(t time.Time) time.Duration {
func (s weekSched) Next(t time.Time) time.Time {
if s.clock == nil {
s.clock = &Clock{}
}
return s.clock.First(t)
return s.clock.Next(t)
}

func (s weekSched) TickerDuration() time.Duration {
Expand Down Expand Up @@ -217,11 +217,11 @@ func (s weekdaySched) IsMatched(t time.Time) bool {
return false
}

func (s weekdaySched) First(t time.Time) time.Duration {
func (s weekdaySched) Next(t time.Time) time.Time {
if s.clock == nil {
s.clock = &Clock{}
}
return s.clock.First(t)
return s.clock.Next(t)
}

func (s weekdaySched) TickerDuration() time.Duration {
Expand Down Expand Up @@ -281,11 +281,11 @@ func (s tickerSched) IsMatched(t time.Time) bool {
return t.Truncate(time.Second).Sub(s.start)%s.d == 0
}

func (s tickerSched) First(t time.Time) time.Duration {
func (s tickerSched) Next(t time.Time) time.Time {
if d := t.Sub(s.start); d > 0 {
return d % s.d
return t.Add(d % s.d)
}
return s.start.Sub(t)
return s.start
}

func (s tickerSched) TickerDuration() time.Duration {
Expand Down
8 changes: 5 additions & 3 deletions scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (sched *Scheduler) init(d time.Duration) error {
t := <-timer.C
sched.sched.init(t)
subscribeNotify(sched.notify)
sched.newTimer(sched.sched.First(t), d)
sched.newTimer(sched.sched.Next(t).Sub(t), d)
return nil
}
return ErrAlreadyRunning
Expand Down Expand Up @@ -119,7 +119,8 @@ func (sched *Scheduler) newTimer(first, duration time.Duration) {
sched.ticker.Stop()
sched.mu.Lock()
defer sched.mu.Unlock()
sched.newTimer(sched.sched.First(time.Now()), duration)
t := time.Now()
sched.newTimer(sched.sched.Next(t).Sub(t), duration)
return
case <-sched.ctx.Done():
sched.ticker.Stop()
Expand All @@ -137,7 +138,8 @@ func (sched *Scheduler) newTimer(first, duration time.Duration) {
case <-sched.notify:
sched.mu.Lock()
if sched.timer.Stop() {
sched.timer.Reset(sched.sched.First(time.Now()))
t := time.Now()
sched.timer.Reset(sched.sched.Next(t).Sub(t))
}
sched.mu.Unlock()
case <-sched.ctx.Done():
Expand Down

0 comments on commit 51ee424

Please sign in to comment.