Begin adding logic for first-party cron
This commit is contained in:
parent
0bb7871832
commit
8eeb438473
64
core/cron.go
Normal file
64
core/cron.go
Normal file
@ -0,0 +1,64 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetNextUpdate(lastUpdated time.Time, spec string) (nextUpdate time.Time, err error) {
|
||||
var nextUpdates []time.Time
|
||||
switch {
|
||||
case strings.HasPrefix(spec, "every "):
|
||||
nextUpdates, err = parseEverySpec(lastUpdated, spec[len("every "):])
|
||||
case strings.HasPrefix(spec, "at "):
|
||||
nextUpdates, err = parseAtSpec(lastUpdated, spec[len("at "):])
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unknown spec format: %v", spec)
|
||||
}
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
for _, next := range nextUpdates {
|
||||
if next.After(lastUpdated) && (nextUpdate.IsZero() || next.Before(nextUpdate)) {
|
||||
nextUpdate = next
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the next instance of the every-spec after the base time.
|
||||
// An every-spec is a Go duration string.
|
||||
func parseEverySpec(base time.Time, everySpec string) (nextUpdates []time.Time, err error) {
|
||||
var duration time.Duration
|
||||
duration, err = time.ParseDuration(everySpec)
|
||||
if err == nil {
|
||||
next := base.Round(duration)
|
||||
if !next.After(base) {
|
||||
next = next.Add(duration)
|
||||
}
|
||||
nextUpdates = []time.Time{next}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the next instances of the at-spec times after the base time.
|
||||
// An at-spec is in the patterm HH:MM[,HH:MM,[...]].
|
||||
func parseAtSpec(base time.Time, atSpec string) (nextUpdates []time.Time, err error) {
|
||||
timeSpecs := strings.Split(atSpec, ",")
|
||||
for _, timeSpec := range timeSpecs {
|
||||
var hour, minute int
|
||||
_, err = fmt.Sscanf(timeSpec, "%d:%d", &hour, &minute)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse %s: %v", timeSpec, err)
|
||||
}
|
||||
// The time instance on the same day as the base time
|
||||
specOfDay := time.Date(base.Year(), base.Month(), base.Day(), hour, minute, 0, 0, base.Location())
|
||||
// Bump it forward one day if it's before the base time
|
||||
if !specOfDay.After(base) {
|
||||
specOfDay = specOfDay.Add(24 * time.Hour)
|
||||
}
|
||||
nextUpdates = append(nextUpdates, specOfDay)
|
||||
}
|
||||
return
|
||||
}
|
109
core/cron_test.go
Normal file
109
core/cron_test.go
Normal file
@ -0,0 +1,109 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetNextUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
lastUpdated time.Time
|
||||
spec string
|
||||
expected time.Time
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 12, 0, 0, 0, time.UTC),
|
||||
spec: "every 1h#1",
|
||||
expected: time.Date(2020, 10, 22, 13, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 12, 0, 5, 0, time.UTC),
|
||||
spec: "every 1h#2",
|
||||
expected: time.Date(2020, 10, 22, 13, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 11, 0, 0, 0, time.UTC),
|
||||
spec: "every 2h30m#1",
|
||||
expected: time.Date(2020, 10, 22, 11, 30, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 11, 30, 0, 0, time.UTC),
|
||||
spec: "every 2h30m#2",
|
||||
expected: time.Date(2020, 10, 22, 14, 00, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 11, 30, 0, 0, time.UTC),
|
||||
spec: "every 3",
|
||||
expected: time.Time{},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 12, 0, 0, 0, time.UTC),
|
||||
spec: "at 14:00#1",
|
||||
expected: time.Date(2020, 10, 22, 14, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 14, 0, 0, 0, time.UTC),
|
||||
spec: "at 14:00#2",
|
||||
expected: time.Date(2020, 10, 23, 14, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 15, 0, 0, 0, time.UTC),
|
||||
spec: "at 14:00#3",
|
||||
expected: time.Date(2020, 10, 23, 14, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 9, 0, 0, 0, time.UTC),
|
||||
spec: "at 10:00,15:00#1",
|
||||
expected: time.Date(2020, 10, 22, 10, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 12, 0, 0, 0, time.UTC),
|
||||
spec: "at 10:00,15:00#2",
|
||||
expected: time.Date(2020, 10, 22, 15, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 15, 0, 0, 0, time.UTC),
|
||||
spec: "at 10:00,15:00#3",
|
||||
expected: time.Date(2020, 10, 23, 10, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
lastUpdated: time.Date(2020, 10, 22, 16, 0, 0, 0, time.UTC),
|
||||
spec: "at 10:00,15:00#4",
|
||||
expected: time.Date(2020, 10, 23, 10, 0, 0, 0, time.UTC),
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.spec, func(t *testing.T) {
|
||||
spec := test.spec
|
||||
if comment := strings.Index(spec, "#"); comment > -1 {
|
||||
spec = spec[:comment]
|
||||
}
|
||||
nextUpdate, err := GetNextUpdate(test.lastUpdated, spec)
|
||||
if test.expectErr && err == nil {
|
||||
t.Error("test did not fail as expected")
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("error: %v", err)
|
||||
}
|
||||
if !nextUpdate.Equal(test.expected) {
|
||||
t.Errorf("\nexpected: %v\n got: %v", test.expected, nextUpdate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user