Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8a1ae18

Browse files
authored
feat: add crontab package for supporting autostart/stop. (#844)
* feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. Fixes #817. * fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fixup! fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. * fix: return struct instead of interface * remove unnecessary interface and export struct * fix: doc comments * rename package to autostart/schedule * address PR comments
1 parent b621c59 commit 8a1ae18

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package schedule_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/coderd/autostart/schedule"
10+
)
11+
12+
func Test_Weekly(t *testing.T) {
13+
t.Parallel()
14+
testCases := []struct {
15+
name string
16+
spec string
17+
at time.Time
18+
expectedNext time.Time
19+
expectedError string
20+
}{
21+
{
22+
name: "with timezone",
23+
spec: "CRON_TZ=US/Central 30 9 1-5",
24+
at: time.Date(2022, 4, 1, 14, 29, 0, 0, time.UTC),
25+
expectedNext: time.Date(2022, 4, 1, 14, 30, 0, 0, time.UTC),
26+
expectedError: "",
27+
},
28+
{
29+
name: "without timezone",
30+
spec: "30 9 1-5",
31+
at: time.Date(2022, 4, 1, 9, 29, 0, 0, time.Local),
32+
expectedNext: time.Date(2022, 4, 1, 9, 30, 0, 0, time.Local),
33+
expectedError: "",
34+
},
35+
{
36+
name: "invalid schedule",
37+
spec: "asdfasdfasdfsd",
38+
at: time.Time{},
39+
expectedNext: time.Time{},
40+
expectedError: "parse schedule: expected exactly 3 fields, found 1: [asdfasdfasdfsd]",
41+
},
42+
{
43+
name: "invalid location",
44+
spec: "CRON_TZ=Fictional/Country 30 9 1-5",
45+
at: time.Time{},
46+
expectedNext: time.Time{},
47+
expectedError: "parse schedule: provided bad location Fictional/Country: unknown time zone Fictional/Country",
48+
},
49+
}
50+
51+
for _, testCase := range testCases {
52+
testCase := testCase
53+
t.Run(testCase.name, func(t *testing.T) {
54+
t.Parallel()
55+
actual, err := schedule.Weekly(testCase.spec)
56+
if testCase.expectedError == "" {
57+
nextTime := actual.Next(testCase.at)
58+
require.NoError(t, err)
59+
require.Equal(t, testCase.expectedNext, nextTime)
60+
require.Equal(t, testCase.spec, actual.String())
61+
} else {
62+
require.EqualError(t, err, testCase.expectedError)
63+
require.Nil(t, actual)
64+
}
65+
})
66+
}
67+
}

coderd/autostart/schedule/schedule.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// package schedule provides utilities for parsing and deserializing
2+
// cron-style expressions.
3+
package schedule
4+
5+
import (
6+
"time"
7+
8+
"github.com/robfig/cron/v3"
9+
"golang.org/x/xerrors"
10+
)
11+
12+
// For the purposes of this library, we only need minute, hour, and
13+
// day-of-week.
14+
const parserFormatWeekly = cron.Minute | cron.Hour | cron.Dow
15+
16+
var defaultParser = cron.NewParser(parserFormatWeekly)
17+
18+
// Weekly parses a Schedule from spec scoped to a recurring weekly event.
19+
// Spec consists of the following space-delimited fields, in the following order:
20+
// - timezone e.g. CRON_TZ=US/Central (optional)
21+
// - minutes of hour e.g. 30 (required)
22+
// - hour of day e.g. 9 (required)
23+
// - day of week e.g. 1 (required)
24+
//
25+
// Example Usage:
26+
// local_sched, _ := schedule.Weekly("59 23 *")
27+
// fmt.Println(sched.Next(time.Now().Format(time.RFC3339)))
28+
// // Output: 2022-04-04T23:59:00Z
29+
// us_sched, _ := schedule.Weekly("CRON_TZ=US/Central 30 9 1-5")
30+
// fmt.Println(sched.Next(time.Now()).Format(time.RFC3339))
31+
// // Output: 2022-04-04T14:30:00Z
32+
func Weekly(spec string) (*Schedule, error) {
33+
specSched, err := defaultParser.Parse(spec)
34+
if err != nil {
35+
return nil, xerrors.Errorf("parse schedule: %w", err)
36+
}
37+
38+
schedule, ok := specSched.(*cron.SpecSchedule)
39+
if !ok {
40+
return nil, xerrors.Errorf("expected *cron.SpecSchedule but got %T", specSched)
41+
}
42+
43+
cronSched := &Schedule{
44+
sched: schedule,
45+
spec: spec,
46+
}
47+
return cronSched, nil
48+
}
49+
50+
// Schedule represents a cron schedule.
51+
// It's essentially a thin wrapper for robfig/cron/v3 that implements Stringer.
52+
type Schedule struct {
53+
sched *cron.SpecSchedule
54+
// XXX: there isn't any nice way for robfig/cron to serialize
55+
spec string
56+
}
57+
58+
// String serializes the schedule to its original human-friendly format.
59+
func (s Schedule) String() string {
60+
return s.spec
61+
}
62+
63+
// Next returns the next time in the schedule relative to t.
64+
func (s Schedule) Next(t time.Time) time.Time {
65+
return s.sched.Next(t)
66+
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ require (
222222
github.com/prometheus/procfs v0.7.3 // indirect
223223
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 // indirect
224224
github.com/rivo/uniseg v0.2.0 // indirect
225+
github.com/robfig/cron/v3 v3.0.1
225226
github.com/russross/blackfriday/v2 v2.1.0 // indirect
226227
github.com/sirupsen/logrus v1.8.1 // indirect
227228
github.com/spf13/afero v1.8.1 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
15011501
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
15021502
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
15031503
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
1504+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
1505+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
15041506
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
15051507
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
15061508
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

0 commit comments

Comments
 (0)