From 50a9b26055c9ace125e1cc6a675a291fad2bc1ce Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Fri, 1 Apr 2022 21:17:12 +0100 Subject: [PATCH 1/9] feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. Fixes #817. --- coderd/crontab/crontab.go | 64 +++++++++++++++++++++++++++++++++ coderd/crontab/crontab_test.go | 65 ++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 132 insertions(+) create mode 100644 coderd/crontab/crontab.go create mode 100644 coderd/crontab/crontab_test.go diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go new file mode 100644 index 0000000000000..7e2094e113f31 --- /dev/null +++ b/coderd/crontab/crontab.go @@ -0,0 +1,64 @@ +package crontab + +import ( + "time" + + "github.com/robfig/cron/v3" + "golang.org/x/xerrors" +) + +const parserFormat = cron.Minute | cron.Hour | cron.Dow + +var defaultParser = cron.NewParser(parserFormat) + +// WeeklySchedule represents a weekly cron schedule serializable to and from a string. +// +// Example Usage: +// local_sched, _ := cron.Parse("59 23 *") +// fmt.Println(sched.Next(time.Now().Format(time.RFC3339))) +// // Output: 2022-04-04T23:59:00Z +// us_sched, _ := cron.Parse("TZ=US/Central 30 9 1-5") +// fmt.Println(sched.Next(time.Now()).Format(time.RFC3339)) +// // Output: 2022-04-04T14:30:00Z +type WeeklySchedule interface { + String() string + Next(time.Time) time.Time +} + +// cronSchedule is a thin wrapper for cron.SpecSchedule that implements Stringer. +type cronSchedule struct { + sched *cron.SpecSchedule + // XXX: there isn't any nice way for robfig/cron to serialize + spec string +} + +var _ WeeklySchedule = (*cronSchedule)(nil) + +// String serializes the schedule to its original human-friendly format. +func (s cronSchedule) String() string { + return s.spec +} + +// Next returns the next time in the schedule relative to t. +func (s cronSchedule) Next(t time.Time) time.Time { + return s.sched.Next(t) +} + +func Parse(spec string) (WeeklySchedule, error) { + s, err := defaultParser.Parse(spec) + if err != nil { + return nil, xerrors.Errorf("parse schedule: %w", err) + } + + schedule, ok := s.(*cron.SpecSchedule) + if !ok { + return nil, xerrors.Errorf("expected cron.SpecSchedule but got %T", s) + } + + cs := &cronSchedule{ + sched: schedule, + spec: spec, + } + return cs, nil + +} diff --git a/coderd/crontab/crontab_test.go b/coderd/crontab/crontab_test.go new file mode 100644 index 0000000000000..e84854d6da8f9 --- /dev/null +++ b/coderd/crontab/crontab_test.go @@ -0,0 +1,65 @@ +package crontab + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func Test_Parse(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + spec string + at time.Time + expectedNext time.Time + expectedError string + }{ + { + name: "with timezone", + spec: "TZ=US/Central 30 9 1-5", + at: time.Date(2022, 4, 1, 14, 29, 0, 0, time.UTC), + expectedNext: time.Date(2022, 4, 1, 14, 30, 0, 0, time.UTC), + expectedError: "", + }, + { + name: "without timezone", + spec: "30 9 1-5", + at: time.Date(2022, 4, 1, 9, 29, 0, 0, time.Local), + expectedNext: time.Date(2022, 4, 1, 9, 30, 0, 0, time.Local), + expectedError: "", + }, + { + name: "invalid schedule", + spec: "asdfasdfasdfsd", + at: time.Time{}, + expectedNext: time.Time{}, + expectedError: "parse schedule: expected exactly 3 fields, found 1: [asdfasdfasdfsd]", + }, + { + name: "invalid location", + spec: "TZ=Fictional/Country 30 9 1-5", + at: time.Time{}, + expectedNext: time.Time{}, + expectedError: "parse schedule: provided bad location Fictional/Country: unknown time zone Fictional/Country", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + actual, err := Parse(tc.spec) + if tc.expectedError == "" { + nextTime := actual.Next(tc.at) + require.NoError(t, err) + require.Equal(t, tc.expectedNext, nextTime) + require.Equal(t, tc.spec, actual.String()) + } else { + require.EqualError(t, err, tc.expectedError) + require.Nil(t, actual) + } + }) + } +} diff --git a/go.mod b/go.mod index 231f3612cac52..aa919e45aa16e 100644 --- a/go.mod +++ b/go.mod @@ -222,6 +222,7 @@ require ( github.com/prometheus/procfs v0.7.3 // indirect github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/robfig/cron/v3 v3.0.1 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.8.1 // indirect diff --git a/go.sum b/go.sum index ef7dc0b6fee6b..0d4abe9d7a2e0 100644 --- a/go.sum +++ b/go.sum @@ -1501,6 +1501,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= From b8ec4fc1faf03dfe29439c2a298593b781439636 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:02:56 +0000 Subject: [PATCH 2/9] fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. --- coderd/crontab/crontab.go | 2 +- coderd/crontab/crontab_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go index 7e2094e113f31..d3d99edb70ef4 100644 --- a/coderd/crontab/crontab.go +++ b/coderd/crontab/crontab.go @@ -17,7 +17,7 @@ var defaultParser = cron.NewParser(parserFormat) // local_sched, _ := cron.Parse("59 23 *") // fmt.Println(sched.Next(time.Now().Format(time.RFC3339))) // // Output: 2022-04-04T23:59:00Z -// us_sched, _ := cron.Parse("TZ=US/Central 30 9 1-5") +// us_sched, _ := cron.Parse("CRON_TZ=US/Central 30 9 1-5") // fmt.Println(sched.Next(time.Now()).Format(time.RFC3339)) // // Output: 2022-04-04T14:30:00Z type WeeklySchedule interface { diff --git a/coderd/crontab/crontab_test.go b/coderd/crontab/crontab_test.go index e84854d6da8f9..eb615ee940f86 100644 --- a/coderd/crontab/crontab_test.go +++ b/coderd/crontab/crontab_test.go @@ -18,7 +18,7 @@ func Test_Parse(t *testing.T) { }{ { name: "with timezone", - spec: "TZ=US/Central 30 9 1-5", + spec: "CRON_TZ=US/Central 30 9 1-5", at: time.Date(2022, 4, 1, 14, 29, 0, 0, time.UTC), expectedNext: time.Date(2022, 4, 1, 14, 30, 0, 0, time.UTC), expectedError: "", @@ -39,7 +39,7 @@ func Test_Parse(t *testing.T) { }, { name: "invalid location", - spec: "TZ=Fictional/Country 30 9 1-5", + spec: "CRON_TZ=Fictional/Country 30 9 1-5", at: time.Time{}, expectedNext: time.Time{}, expectedError: "parse schedule: provided bad location Fictional/Country: unknown time zone Fictional/Country", From c92c8eb15eb0c8dc6ebebd3aef0f52a156645ee7 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:09:00 +0000 Subject: [PATCH 3/9] fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. --- coderd/crontab/crontab.go | 10 +++++----- coderd/crontab/crontab_test.go | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go index d3d99edb70ef4..8225467c06c5c 100644 --- a/coderd/crontab/crontab.go +++ b/coderd/crontab/crontab.go @@ -45,20 +45,20 @@ func (s cronSchedule) Next(t time.Time) time.Time { } func Parse(spec string) (WeeklySchedule, error) { - s, err := defaultParser.Parse(spec) + specSched, err := defaultParser.Parse(spec) if err != nil { return nil, xerrors.Errorf("parse schedule: %w", err) } - schedule, ok := s.(*cron.SpecSchedule) + schedule, ok := specSched.(*cron.SpecSchedule) if !ok { - return nil, xerrors.Errorf("expected cron.SpecSchedule but got %T", s) + return nil, xerrors.Errorf("expected *cron.SpecSchedule but got %T", specSched) } - cs := &cronSchedule{ + cronSched := &cronSchedule{ sched: schedule, spec: spec, } - return cs, nil + return cronSched, nil } diff --git a/coderd/crontab/crontab_test.go b/coderd/crontab/crontab_test.go index eb615ee940f86..feab86faf647b 100644 --- a/coderd/crontab/crontab_test.go +++ b/coderd/crontab/crontab_test.go @@ -1,9 +1,10 @@ -package crontab +package crontab_test import ( "testing" "time" + "github.com/coder/coder/coderd/cron" "github.com/stretchr/testify/require" ) @@ -46,18 +47,18 @@ func Test_Parse(t *testing.T) { }, } - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { t.Parallel() - actual, err := Parse(tc.spec) - if tc.expectedError == "" { - nextTime := actual.Next(tc.at) + actual, err := cron.Parse(testCase.spec) + if testCase.expectedError == "" { + nextTime := actual.Next(testCase.at) require.NoError(t, err) - require.Equal(t, tc.expectedNext, nextTime) - require.Equal(t, tc.spec, actual.String()) + require.Equal(t, testCase.expectedNext, nextTime) + require.Equal(t, testCase.spec, actual.String()) } else { - require.EqualError(t, err, tc.expectedError) + require.EqualError(t, err, testCase.expectedError) require.Nil(t, actual) } }) From bd02289910c7dd78725a3c40a3300d5a637359c4 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:09:52 +0000 Subject: [PATCH 4/9] fixup! fixup! feat: add crontab package for supporting autostart/stop. This is basically a small wrapper around robfig/cron/v3. --- coderd/crontab/crontab_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/crontab/crontab_test.go b/coderd/crontab/crontab_test.go index feab86faf647b..3c68f2bc0b933 100644 --- a/coderd/crontab/crontab_test.go +++ b/coderd/crontab/crontab_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/coder/coder/coderd/cron" + "github.com/coder/coder/coderd/crontab" "github.com/stretchr/testify/require" ) @@ -51,7 +51,7 @@ func Test_Parse(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - actual, err := cron.Parse(testCase.spec) + actual, err := crontab.Parse(testCase.spec) if testCase.expectedError == "" { nextTime := actual.Next(testCase.at) require.NoError(t, err) From 45253a9f68c9b5a2ae1f69437bcb6740aea7f7eb Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:11:36 +0000 Subject: [PATCH 5/9] fix: return struct instead of interface --- coderd/crontab/crontab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go index 8225467c06c5c..cdb014bedc7b8 100644 --- a/coderd/crontab/crontab.go +++ b/coderd/crontab/crontab.go @@ -44,7 +44,7 @@ func (s cronSchedule) Next(t time.Time) time.Time { return s.sched.Next(t) } -func Parse(spec string) (WeeklySchedule, error) { +func Parse(spec string) (*cronSchedule, error) { specSched, err := defaultParser.Parse(spec) if err != nil { return nil, xerrors.Errorf("parse schedule: %w", err) From b9b7a5a0a23722832e82cbcdb615f71ee467eea9 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:14:13 +0000 Subject: [PATCH 6/9] remove unnecessary interface and export struct --- coderd/crontab/crontab.go | 21 +++++++-------------- coderd/crontab/crontab_test.go | 3 ++- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go index cdb014bedc7b8..48a6803cba4c4 100644 --- a/coderd/crontab/crontab.go +++ b/coderd/crontab/crontab.go @@ -11,7 +11,7 @@ const parserFormat = cron.Minute | cron.Hour | cron.Dow var defaultParser = cron.NewParser(parserFormat) -// WeeklySchedule represents a weekly cron schedule serializable to and from a string. +// CronSchedule represents a weekly cron schedule serializable to and from a string. // // Example Usage: // local_sched, _ := cron.Parse("59 23 *") @@ -20,31 +20,25 @@ var defaultParser = cron.NewParser(parserFormat) // us_sched, _ := cron.Parse("CRON_TZ=US/Central 30 9 1-5") // fmt.Println(sched.Next(time.Now()).Format(time.RFC3339)) // // Output: 2022-04-04T14:30:00Z -type WeeklySchedule interface { - String() string - Next(time.Time) time.Time -} -// cronSchedule is a thin wrapper for cron.SpecSchedule that implements Stringer. -type cronSchedule struct { +// WeeklySchedule is a thin wrapper for cron.SpecSchedule that implements Stringer. +type WeeklySchedule struct { sched *cron.SpecSchedule // XXX: there isn't any nice way for robfig/cron to serialize spec string } -var _ WeeklySchedule = (*cronSchedule)(nil) - // String serializes the schedule to its original human-friendly format. -func (s cronSchedule) String() string { +func (s WeeklySchedule) String() string { return s.spec } // Next returns the next time in the schedule relative to t. -func (s cronSchedule) Next(t time.Time) time.Time { +func (s WeeklySchedule) Next(t time.Time) time.Time { return s.sched.Next(t) } -func Parse(spec string) (*cronSchedule, error) { +func Parse(spec string) (*WeeklySchedule, error) { specSched, err := defaultParser.Parse(spec) if err != nil { return nil, xerrors.Errorf("parse schedule: %w", err) @@ -55,10 +49,9 @@ func Parse(spec string) (*cronSchedule, error) { return nil, xerrors.Errorf("expected *cron.SpecSchedule but got %T", specSched) } - cronSched := &cronSchedule{ + cronSched := &WeeklySchedule{ sched: schedule, spec: spec, } return cronSched, nil - } diff --git a/coderd/crontab/crontab_test.go b/coderd/crontab/crontab_test.go index 3c68f2bc0b933..10821d1621080 100644 --- a/coderd/crontab/crontab_test.go +++ b/coderd/crontab/crontab_test.go @@ -4,8 +4,9 @@ import ( "testing" "time" - "github.com/coder/coder/coderd/crontab" "github.com/stretchr/testify/require" + + "github.com/coder/coder/coderd/crontab" ) func Test_Parse(t *testing.T) { From 91cc3b62b7d49a43fd48debb8a8fc9c85d0f4199 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 11:18:15 +0000 Subject: [PATCH 7/9] fix: doc comments --- coderd/crontab/crontab.go | 42 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/coderd/crontab/crontab.go b/coderd/crontab/crontab.go index 48a6803cba4c4..6054cdcf9f45b 100644 --- a/coderd/crontab/crontab.go +++ b/coderd/crontab/crontab.go @@ -1,3 +1,5 @@ +// package crontab provides utilities for parsing and deserializing +// cron-style expressions. package crontab import ( @@ -7,11 +9,13 @@ import ( "golang.org/x/xerrors" ) +// For the purposes of this library, we only need minute, hour, and +//day-of-week. const parserFormat = cron.Minute | cron.Hour | cron.Dow var defaultParser = cron.NewParser(parserFormat) -// CronSchedule represents a weekly cron schedule serializable to and from a string. +// Parse parses a WeeklySchedule from spec. // // Example Usage: // local_sched, _ := cron.Parse("59 23 *") @@ -20,24 +24,6 @@ var defaultParser = cron.NewParser(parserFormat) // us_sched, _ := cron.Parse("CRON_TZ=US/Central 30 9 1-5") // fmt.Println(sched.Next(time.Now()).Format(time.RFC3339)) // // Output: 2022-04-04T14:30:00Z - -// WeeklySchedule is a thin wrapper for cron.SpecSchedule that implements Stringer. -type WeeklySchedule struct { - sched *cron.SpecSchedule - // XXX: there isn't any nice way for robfig/cron to serialize - spec string -} - -// String serializes the schedule to its original human-friendly format. -func (s WeeklySchedule) String() string { - return s.spec -} - -// Next returns the next time in the schedule relative to t. -func (s WeeklySchedule) Next(t time.Time) time.Time { - return s.sched.Next(t) -} - func Parse(spec string) (*WeeklySchedule, error) { specSched, err := defaultParser.Parse(spec) if err != nil { @@ -55,3 +41,21 @@ func Parse(spec string) (*WeeklySchedule, error) { } return cronSched, nil } + +// WeeklySchedule represents a weekly cron schedule. +// It's essentially a thin wrapper for robfig/cron/v3 that implements Stringer. +type WeeklySchedule struct { + sched *cron.SpecSchedule + // XXX: there isn't any nice way for robfig/cron to serialize + spec string +} + +// String serializes the schedule to its original human-friendly format. +func (s WeeklySchedule) String() string { + return s.spec +} + +// Next returns the next time in the schedule relative to t. +func (s WeeklySchedule) Next(t time.Time) time.Time { + return s.sched.Next(t) +} From 193d600a916a653ca87237e93e453a365285ec29 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 18:47:44 +0000 Subject: [PATCH 8/9] rename package to autostart/schedule --- coderd/{crontab => autostart/schedule}/crontab.go | 0 coderd/{crontab => autostart/schedule}/crontab_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/{crontab => autostart/schedule}/crontab.go (100%) rename coderd/{crontab => autostart/schedule}/crontab_test.go (100%) diff --git a/coderd/crontab/crontab.go b/coderd/autostart/schedule/crontab.go similarity index 100% rename from coderd/crontab/crontab.go rename to coderd/autostart/schedule/crontab.go diff --git a/coderd/crontab/crontab_test.go b/coderd/autostart/schedule/crontab_test.go similarity index 100% rename from coderd/crontab/crontab_test.go rename to coderd/autostart/schedule/crontab_test.go From 32f0cb22ee566508d1628c90d996909ce655af9e Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 4 Apr 2022 18:56:03 +0000 Subject: [PATCH 9/9] address PR comments --- coderd/autostart/schedule/crontab_test.go | 8 ++--- .../schedule/{crontab.go => schedule.go} | 33 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) rename coderd/autostart/schedule/{crontab.go => schedule.go} (55%) diff --git a/coderd/autostart/schedule/crontab_test.go b/coderd/autostart/schedule/crontab_test.go index 10821d1621080..63383f38e6338 100644 --- a/coderd/autostart/schedule/crontab_test.go +++ b/coderd/autostart/schedule/crontab_test.go @@ -1,4 +1,4 @@ -package crontab_test +package schedule_test import ( "testing" @@ -6,10 +6,10 @@ import ( "github.com/stretchr/testify/require" - "github.com/coder/coder/coderd/crontab" + "github.com/coder/coder/coderd/autostart/schedule" ) -func Test_Parse(t *testing.T) { +func Test_Weekly(t *testing.T) { t.Parallel() testCases := []struct { name string @@ -52,7 +52,7 @@ func Test_Parse(t *testing.T) { testCase := testCase t.Run(testCase.name, func(t *testing.T) { t.Parallel() - actual, err := crontab.Parse(testCase.spec) + actual, err := schedule.Weekly(testCase.spec) if testCase.expectedError == "" { nextTime := actual.Next(testCase.at) require.NoError(t, err) diff --git a/coderd/autostart/schedule/crontab.go b/coderd/autostart/schedule/schedule.go similarity index 55% rename from coderd/autostart/schedule/crontab.go rename to coderd/autostart/schedule/schedule.go index 6054cdcf9f45b..11cd8a0030ec6 100644 --- a/coderd/autostart/schedule/crontab.go +++ b/coderd/autostart/schedule/schedule.go @@ -1,6 +1,6 @@ -// package crontab provides utilities for parsing and deserializing +// package schedule provides utilities for parsing and deserializing // cron-style expressions. -package crontab +package schedule import ( "time" @@ -10,21 +10,26 @@ import ( ) // For the purposes of this library, we only need minute, hour, and -//day-of-week. -const parserFormat = cron.Minute | cron.Hour | cron.Dow +// day-of-week. +const parserFormatWeekly = cron.Minute | cron.Hour | cron.Dow -var defaultParser = cron.NewParser(parserFormat) +var defaultParser = cron.NewParser(parserFormatWeekly) -// Parse parses a WeeklySchedule from spec. +// Weekly parses a Schedule from spec scoped to a recurring weekly event. +// Spec consists of the following space-delimited fields, in the following order: +// - timezone e.g. CRON_TZ=US/Central (optional) +// - minutes of hour e.g. 30 (required) +// - hour of day e.g. 9 (required) +// - day of week e.g. 1 (required) // // Example Usage: -// local_sched, _ := cron.Parse("59 23 *") +// local_sched, _ := schedule.Weekly("59 23 *") // fmt.Println(sched.Next(time.Now().Format(time.RFC3339))) // // Output: 2022-04-04T23:59:00Z -// us_sched, _ := cron.Parse("CRON_TZ=US/Central 30 9 1-5") +// us_sched, _ := schedule.Weekly("CRON_TZ=US/Central 30 9 1-5") // fmt.Println(sched.Next(time.Now()).Format(time.RFC3339)) // // Output: 2022-04-04T14:30:00Z -func Parse(spec string) (*WeeklySchedule, error) { +func Weekly(spec string) (*Schedule, error) { specSched, err := defaultParser.Parse(spec) if err != nil { return nil, xerrors.Errorf("parse schedule: %w", err) @@ -35,27 +40,27 @@ func Parse(spec string) (*WeeklySchedule, error) { return nil, xerrors.Errorf("expected *cron.SpecSchedule but got %T", specSched) } - cronSched := &WeeklySchedule{ + cronSched := &Schedule{ sched: schedule, spec: spec, } return cronSched, nil } -// WeeklySchedule represents a weekly cron schedule. +// Schedule represents a cron schedule. // It's essentially a thin wrapper for robfig/cron/v3 that implements Stringer. -type WeeklySchedule struct { +type Schedule struct { sched *cron.SpecSchedule // XXX: there isn't any nice way for robfig/cron to serialize spec string } // String serializes the schedule to its original human-friendly format. -func (s WeeklySchedule) String() string { +func (s Schedule) String() string { return s.spec } // Next returns the next time in the schedule relative to t. -func (s WeeklySchedule) Next(t time.Time) time.Time { +func (s Schedule) Next(t time.Time) time.Time { return s.sched.Next(t) }