Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pitluga committed Jan 5, 2016
0 parents commit e6917c7
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 0 deletions.
103 changes: 103 additions & 0 deletions pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package cron

import(
"fmt"
"regexp"
"strconv"
"strings"
"time"
)

type UnitMatcher interface {
Matches(timePart int) bool
}

type Wildcard struct {
subpattern string
}

func (_ *Wildcard) Matches(timePart int) bool {
return true
}

type Constant struct {
subpattern string
value int
}

func (constant *Constant) Matches(timePart int) bool {
return constant.value == timePart
}

type Range struct {
subpattern string
min, max int
}

func (r *Range) Matches(timePart int) bool {
return timePart >= r.min && timePart <= r.max
}

type CronSchedule struct {
minutes UnitMatcher
hours UnitMatcher
dayOfMonth UnitMatcher
monthOfYear UnitMatcher
dayOfWeek UnitMatcher
year UnitMatcher
}

type Schedule interface {
ShouldRun(t time.Time) bool
}

func (schedule CronSchedule) ShouldRun(t time.Time) bool {
return schedule.minutes.Matches(t.Minute()) && schedule.hours.Matches(t.Hour()) && schedule.dayOfMonth.Matches(t.Day()) && schedule.monthOfYear.Matches(int(t.Month())) && schedule.dayOfWeek.Matches(int(t.Weekday())) && schedule.year.Matches(t.Year())

}

func parsePart(subpattern string) UnitMatcher {
constantRegexp, err := regexp.Compile(`\A\d+\z`)
if err != nil {
panic("invalid hardcoded regexp!")
}
rangeRegexp, err := regexp.Compile(`\A\d+\-\d+\z`)
if err != nil {
panic("invalid hardcoded regexp!")
}

if "*" == subpattern {
return &Wildcard{subpattern}
} else if constantRegexp.MatchString(subpattern) {
number, err := strconv.Atoi(subpattern)
if err != nil {
panic(fmt.Sprintf("regexp matched integer, but couldn't convert %q to int", subpattern))
}
return &Constant{subpattern, number}
} else if rangeRegexp.MatchString(subpattern) {
rangeParts := strings.Split(subpattern, "-")
min, err := strconv.Atoi(rangeParts[0])
if err != nil {
panic(fmt.Sprintf("regexp matched integer, but couldn't convert %q to int", rangeParts[0]))
}
max, err := strconv.Atoi(rangeParts[1])
if err != nil {
panic(fmt.Sprintf("regexp matched integer, but couldn't convert %q to int", rangeParts[1]))
}
return &Range{subpattern, min, max}
}
return nil
}

func Parse(pattern string) Schedule {
parts := strings.Split(pattern, " ")

return &CronSchedule{
minutes: parsePart(parts[0]),
hours: parsePart(parts[1]),
dayOfMonth: parsePart(parts[2]),
monthOfYear: parsePart(parts[3]),
dayOfWeek: parsePart(parts[4]),
year: parsePart(parts[4]),
}
}
46 changes: 46 additions & 0 deletions pattern_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cron

import(
"testing"
"time"
)

type TimeTestCase struct {
when time.Time
runs bool
}

func IsScheduled(t *testing.T, pattern string, tests []TimeTestCase) {
schedule := Parse(pattern)

for _, test := range(tests) {
if test.runs != schedule.ShouldRun(test.when) {
should := ""
if !test.runs {
should = "not "
}

t.Errorf("pattern %q should %vrun at time %v", pattern, should, test.when)
}
}
}

func TestAllStars(t *testing.T) {
IsScheduled(t, "* * * * * *", []TimeTestCase{{time.Now(), true}})
}

func TestFiveMinutesAfterTheHour(t *testing.T) {
IsScheduled(t, "5 * * * * *", []TimeTestCase{
{time.Date(2016, time.January, 5, 10, 15, 0, 0, time.UTC), false},
{time.Date(2016, time.January, 5, 10, 5, 0, 0, time.UTC), true},
})
}

func TestRanges(t *testing.T) {
IsScheduled(t, "4-8 * * * * *", []TimeTestCase{
{time.Date(2016, time.January, 5, 10, 15, 0, 0, time.UTC), false},
{time.Date(2016, time.January, 5, 10, 5, 0, 0, time.UTC), true},
{time.Date(2016, time.January, 5, 10, 4, 0, 0, time.UTC), true},
{time.Date(2016, time.January, 5, 10, 8, 0, 0, time.UTC), true},
})
}

0 comments on commit e6917c7

Please sign in to comment.