forked from robfig/cron
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add seconds, N/step handling, named months/dows, checking activation …
…at a time, a bunch of cron spec tests.
- Loading branch information
Showing
4 changed files
with
271 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,86 @@ | ||
// This library implements a cron spec parser and runner. See the README for | ||
// more details. | ||
package cron | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
// Cron keeps track of any number of entries, invoking the associated func as | ||
// specified by the spec. See http://en.wikipedia.org/wiki/Cron | ||
// It may be started and stopped. | ||
type Cron struct { | ||
Entries []*Entry | ||
stop chan struct{} | ||
} | ||
|
||
// A cron entry consists of a schedule and the func to execute on that schedule. | ||
type Entry struct { | ||
*Schedule | ||
Func func() | ||
} | ||
|
||
func New() *Cron { | ||
return new(Cron) | ||
} | ||
|
||
func (c *Cron) Add(spec string, cmd func()) { | ||
c.Entries = append(c.Entries, &Entry{Parse(spec), cmd}) | ||
} | ||
|
||
func (c *Cron) Run() { | ||
ticker := time.Tick(1 * time.Minute) | ||
for { | ||
select { | ||
case now := <-ticker: | ||
for _, entry := range c.Entries { | ||
if matches(now, entry.Schedule) { | ||
go entry.Func() | ||
} | ||
} | ||
|
||
case <-c.stop: | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (c Cron) Stop() { | ||
c.stop <- struct{}{} | ||
} | ||
|
||
// Return true if the given entries overlap. | ||
func matches(t time.Time, sched *Schedule) bool { | ||
var ( | ||
domMatch bool = 1<<uint(t.Day())&sched.Dom > 0 | ||
dowMatch bool = 1<<uint(t.Weekday())&sched.Dow > 0 | ||
dayMatch bool | ||
) | ||
|
||
if sched.Dom&STAR_BIT > 0 || sched.Dow&STAR_BIT > 0 { | ||
dayMatch = domMatch && dowMatch | ||
} else { | ||
dayMatch = domMatch || dowMatch | ||
} | ||
|
||
return 1<<uint(t.Minute())&sched.Minute > 0 && | ||
1<<uint(t.Hour())&sched.Hour > 0 && | ||
1<<uint(t.Month())&sched.Month > 0 && | ||
dayMatch | ||
} | ||
|
||
// // Return the number of units betwee now and then. | ||
// func difference(then, now uint64, r bounds) uint { | ||
// // Shift the current time fields left (and around) until & is non-zero. | ||
// i := 0 | ||
// for then & now << ((i - r.min) % (r.max - r.min + 1) + r.min) == 0 { | ||
// // A guard against no units selected. | ||
// if i > r.max { | ||
// panic("Entry had no minute/hour selected.") | ||
// } | ||
|
||
// i++ | ||
// } | ||
|
||
// return i | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package cron | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestActivation(t *testing.T) { | ||
tests := []struct { | ||
time, spec string | ||
expected bool | ||
}{ | ||
// Every fifteen minutes. | ||
{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true}, | ||
{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true}, | ||
{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false}, | ||
|
||
// Every fifteen minutes, starting at 5 minutes. | ||
{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true}, | ||
{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true}, | ||
{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true}, | ||
|
||
// Named months | ||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true}, | ||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false}, | ||
|
||
// Everything set. | ||
{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true}, | ||
{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true}, | ||
{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false}, | ||
{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false}, | ||
|
||
// Predefined schedules | ||
{"Mon Jul 9 15:00 2012", "@hourly", true}, | ||
{"Mon Jul 9 15:04 2012", "@hourly", false}, | ||
{"Mon Jul 9 15:00 2012", "@daily", false}, | ||
{"Mon Jul 9 00:00 2012", "@daily", true}, | ||
{"Mon Jul 9 00:00 2012", "@weekly", false}, | ||
{"Sun Jul 8 00:00 2012", "@weekly", true}, | ||
{"Sun Jul 8 01:00 2012", "@weekly", false}, | ||
{"Sun Jul 8 00:00 2012", "@monthly", false}, | ||
{"Sun Jul 1 00:00 2012", "@monthly", true}, | ||
|
||
// Test interaction of DOW and DOM. | ||
// If both are specified, then only one needs to match. | ||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true}, | ||
{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true}, | ||
{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true}, | ||
|
||
// However, if one has a star, then both need to match. | ||
{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false}, | ||
{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false}, | ||
{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false}, | ||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true}, | ||
} | ||
|
||
for _, test := range tests { | ||
actual := matches(getTime(test.time), Parse(test.spec)) | ||
if test.expected != actual { | ||
t.Logf("Actual Minutes mask: %b", Parse(test.spec).Minute) | ||
t.Errorf("Fail evaluating %s on %s: (expected) %t != %t (actual)", | ||
test.spec, test.time, test.expected, actual) | ||
} | ||
} | ||
} | ||
|
||
func getTime(value string) time.Time { | ||
t, err := time.Parse("Mon Jan 2 15:04 2006", value) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return t | ||
} |
Oops, something went wrong.