瀏覽代碼

Move cron module to independent package

Make it easier to keep track of upstream changes and bug fixes
Unknwon 9 年之前
父節點
當前提交
d8a994ef24

+ 1 - 0
.gopmfile

@@ -17,6 +17,7 @@ github.com/go-sql-driver/mysql = commit:b4db83c
 github.com/go-xorm/core = commit:1e2868c
 github.com/go-xorm/xorm = 
 github.com/gogits/chardet = commit:2404f77725
+github.com/gogits/cron = 
 github.com/gogits/git-module = commit:3b40eae
 github.com/gogits/go-gogs-client = 
 github.com/issue9/identicon = commit:f8c0d2c

+ 0 - 59
models/cron/cron.go

@@ -1,59 +0,0 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package cron
-
-import (
-	"time"
-
-	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/modules/cron"
-	"github.com/gogits/gogs/modules/log"
-	"github.com/gogits/gogs/modules/setting"
-)
-
-var c = cron.New()
-
-func NewContext() {
-	var (
-		entry *cron.Entry
-		err   error
-	)
-	if setting.Cron.UpdateMirror.Enabled {
-		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate)
-		if err != nil {
-			log.Fatal(4, "Cron[Update mirrors]: %v", err)
-		}
-		if setting.Cron.UpdateMirror.RunAtStart {
-			entry.Prev = time.Now()
-			go models.MirrorUpdate()
-		}
-	}
-	if setting.Cron.RepoHealthCheck.Enabled {
-		entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck)
-		if err != nil {
-			log.Fatal(4, "Cron[Repository health check]: %v", err)
-		}
-		if setting.Cron.RepoHealthCheck.RunAtStart {
-			entry.Prev = time.Now()
-			go models.GitFsck()
-		}
-	}
-	if setting.Cron.CheckRepoStats.Enabled {
-		entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats)
-		if err != nil {
-			log.Fatal(4, "Cron[Check repository statistics]: %v", err)
-		}
-		if setting.Cron.CheckRepoStats.RunAtStart {
-			entry.Prev = time.Now()
-			go models.CheckRepoStats()
-		}
-	}
-	c.Start()
-}
-
-// ListTasks returns all running cron tasks.
-func ListTasks() []*cron.Entry {
-	return c.Entries()
-}

+ 0 - 27
modules/cron/constantdelay.go

@@ -1,27 +0,0 @@
-package cron
-
-import "time"
-
-// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
-// It does not support jobs more frequent than once a second.
-type ConstantDelaySchedule struct {
-	Delay time.Duration
-}
-
-// Every returns a crontab Schedule that activates once every duration.
-// Delays of less than a second are not supported (will round up to 1 second).
-// Any fields less than a Second are truncated.
-func Every(duration time.Duration) ConstantDelaySchedule {
-	if duration < time.Second {
-		duration = time.Second
-	}
-	return ConstantDelaySchedule{
-		Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
-	}
-}
-
-// Next returns the next time this should be run.
-// This rounds so that the next activation time will be on the second.
-func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
-	return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
-}

+ 0 - 54
modules/cron/constantdelay_test.go

@@ -1,54 +0,0 @@
-package cron
-
-import (
-	"testing"
-	"time"
-)
-
-func TestConstantDelayNext(t *testing.T) {
-	tests := []struct {
-		time     string
-		delay    time.Duration
-		expected string
-	}{
-		// Simple cases
-		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
-		{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
-
-		// Wrap around hours
-		{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
-
-		// Wrap around days
-		{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
-		{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
-		{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
-
-		// Wrap around months
-		{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
-
-		// Wrap around minute, hour, day, month, and year
-		{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
-
-		// Round to nearest second on the delay
-		{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-
-		// Round up to 1 second if the duration is less.
-		{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
-
-		// Round to nearest second when calculating the next time.
-		{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
-
-		// Round to nearest second for both.
-		{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
-	}
-
-	for _, c := range tests {
-		actual := Every(c.delay).Next(getTime(c.time))
-		expected := getTime(c.expected)
-		if actual != expected {
-			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
-		}
-	}
-}

+ 44 - 193
modules/cron/cron.go

@@ -1,4 +1,3 @@
-// Copyright 2012 Rob Figueiredo. All rights reserved.
 // Copyright 2014 The Gogs Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
@@ -6,207 +5,59 @@
 package cron
 
 import (
-	"sort"
 	"time"
-)
-
-// Cron keeps track of any number of entries, invoking the associated func as
-// specified by the schedule. It may be started, stopped, and the entries may
-// be inspected while running.
-type Cron struct {
-	entries  []*Entry
-	stop     chan struct{}
-	add      chan *Entry
-	snapshot chan []*Entry
-	running  bool
-}
-
-// Job is an interface for submitted cron jobs.
-type Job interface {
-	Run()
-}
-
-// The Schedule describes a job's duty cycle.
-type Schedule interface {
-	// Return the next activation time, later than the given time.
-	// Next is invoked initially, and then each time the job is run.
-	Next(time.Time) time.Time
-}
-
-// Entry consists of a schedule and the func to execute on that schedule.
-type Entry struct {
-	Description string
-	Spec        string
-
-	// The schedule on which this job should be run.
-	Schedule Schedule
-
-	// The next time the job will run. This is the zero time if Cron has not been
-	// started or this entry's schedule is unsatisfiable
-	Next time.Time
-
-	// The last time this job was run. This is the zero time if the job has never
-	// been run.
-	Prev time.Time
-
-	// The Job to run.
-	Job Job
-
-	ExecTimes int // Execute times count.
-}
-
-// byTime is a wrapper for sorting the entry array by time
-// (with zero time at the end).
-type byTime []*Entry
-
-func (s byTime) Len() int      { return len(s) }
-func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s byTime) Less(i, j int) bool {
-	// Two zero times should return false.
-	// Otherwise, zero is "greater" than any other time.
-	// (To sort it at the end of the list.)
-	if s[i].Next.IsZero() {
-		return false
-	}
-	if s[j].Next.IsZero() {
-		return true
-	}
-	return s[i].Next.Before(s[j].Next)
-}
-
-// New returns a new Cron job runner.
-func New() *Cron {
-	return &Cron{
-		entries:  nil,
-		add:      make(chan *Entry),
-		stop:     make(chan struct{}),
-		snapshot: make(chan []*Entry),
-		running:  false,
-	}
-}
-
-// A wrapper that turns a func() into a cron.Job
-type FuncJob func()
-
-func (f FuncJob) Run() { f() }
-
-// AddFunc adds a func to the Cron to be run on the given schedule.
-func (c *Cron) AddFunc(desc, spec string, cmd func()) (*Entry, error) {
-	return c.AddJob(desc, spec, FuncJob(cmd))
-}
 
-// AddFunc adds a Job to the Cron to be run on the given schedule.
-func (c *Cron) AddJob(desc, spec string, cmd Job) (*Entry, error) {
-	schedule, err := Parse(spec)
-	if err != nil {
-		return nil, err
-	}
-	return c.Schedule(desc, spec, schedule, cmd), nil
-}
+	"github.com/gogits/cron"
 
-// Schedule adds a Job to the Cron to be run on the given schedule.
-func (c *Cron) Schedule(desc, spec string, schedule Schedule, cmd Job) *Entry {
-	entry := &Entry{
-		Description: desc,
-		Spec:        spec,
-		Schedule:    schedule,
-		Job:         cmd,
-	}
-	if c.running {
-		c.add <- entry
-	} else {
-		c.entries = append(c.entries, entry)
-	}
-	return entry
-}
+	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/setting"
+)
 
-// Entries returns a snapshot of the cron entries.
-func (c *Cron) Entries() []*Entry {
-	if c.running {
-		c.snapshot <- nil
-		x := <-c.snapshot
-		return x
+var c = cron.New()
+
+func NewContext() {
+	var (
+		entry *cron.Entry
+		err   error
+	)
+	if setting.Cron.UpdateMirror.Enabled {
+		entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate)
+		if err != nil {
+			log.Fatal(4, "Cron[Update mirrors]: %v", err)
+		}
+		if setting.Cron.UpdateMirror.RunAtStart {
+			entry.Prev = time.Now()
+			entry.ExecTimes++
+			go models.MirrorUpdate()
+		}
 	}
-	return c.entrySnapshot()
-}
-
-// Start the cron scheduler in its own go-routine.
-func (c *Cron) Start() {
-	c.running = true
-	go c.run()
-}
-
-// Run the scheduler.. this is private just due to the need to synchronize
-// access to the 'running' state variable.
-func (c *Cron) run() {
-	// Figure out the next activation times for each entry.
-	now := time.Now().Local()
-	for _, entry := range c.entries {
-		entry.Next = entry.Schedule.Next(now)
+	if setting.Cron.RepoHealthCheck.Enabled {
+		entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck)
+		if err != nil {
+			log.Fatal(4, "Cron[Repository health check]: %v", err)
+		}
+		if setting.Cron.RepoHealthCheck.RunAtStart {
+			entry.Prev = time.Now()
+			entry.ExecTimes++
+			go models.GitFsck()
+		}
 	}
-
-	for {
-		// Determine the next entry to run.
-		sort.Sort(byTime(c.entries))
-
-		var effective time.Time
-		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
-			// If there are no entries yet, just sleep - it still handles new entries
-			// and stop requests.
-			effective = now.AddDate(10, 0, 0)
-		} else {
-			effective = c.entries[0].Next
+	if setting.Cron.CheckRepoStats.Enabled {
+		entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats)
+		if err != nil {
+			log.Fatal(4, "Cron[Check repository statistics]: %v", err)
 		}
-
-		select {
-		case now = <-time.After(effective.Sub(now)):
-			// Run every entry whose next time was this effective time.
-			for _, e := range c.entries {
-				if e.Next != effective {
-					break
-				}
-				go e.Job.Run()
-				e.ExecTimes++
-				e.Prev = e.Next
-				e.Next = e.Schedule.Next(effective)
-			}
-			continue
-
-		case newEntry := <-c.add:
-			c.entries = append(c.entries, newEntry)
-			newEntry.Next = newEntry.Schedule.Next(now)
-
-		case <-c.snapshot:
-			c.snapshot <- c.entrySnapshot()
-
-		case <-c.stop:
-			return
+		if setting.Cron.CheckRepoStats.RunAtStart {
+			entry.Prev = time.Now()
+			entry.ExecTimes++
+			go models.CheckRepoStats()
 		}
-
-		// 'now' should be updated after newEntry and snapshot cases.
-		now = time.Now().Local()
 	}
+	c.Start()
 }
 
-// Stop the cron scheduler.
-func (c *Cron) Stop() {
-	c.stop <- struct{}{}
-	c.running = false
-}
-
-// entrySnapshot returns a copy of the current cron entry list.
-func (c *Cron) entrySnapshot() []*Entry {
-	entries := make([]*Entry, 0, len(c.entries))
-	for _, e := range c.entries {
-		entries = append(entries, &Entry{
-			Description: e.Description,
-			Spec:        e.Spec,
-			Schedule:    e.Schedule,
-			Next:        e.Next,
-			Prev:        e.Prev,
-			Job:         e.Job,
-			ExecTimes:   e.ExecTimes,
-		})
-	}
-	return entries
+// ListTasks returns all running cron tasks.
+func ListTasks() []*cron.Entry {
+	return c.Entries()
 }

+ 0 - 255
modules/cron/cron_test.go

@@ -1,255 +0,0 @@
-package cron
-
-import (
-	"fmt"
-	"sync"
-	"testing"
-	"time"
-)
-
-// Many tests schedule a job for every second, and then wait at most a second
-// for it to run.  This amount is just slightly larger than 1 second to
-// compensate for a few milliseconds of runtime.
-const ONE_SECOND = 1*time.Second + 10*time.Millisecond
-
-// Start and stop cron with no entries.
-func TestNoEntries(t *testing.T) {
-	cron := New()
-	cron.Start()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-stop(cron):
-	}
-}
-
-// Start, stop, then add an entry. Verify entry doesn't run.
-func TestStopCausesJobsToNotRun(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.Start()
-	cron.Stop()
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-
-	select {
-	case <-time.After(ONE_SECOND):
-		// No job ran!
-	case <-wait(wg):
-		t.FailNow()
-	}
-}
-
-// Add a job, start cron, expect it runs.
-func TestAddBeforeRunning(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-	cron.Start()
-	defer cron.Stop()
-
-	// Give cron 2 seconds to run our job (which is always activated).
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Start cron, add a job, expect it runs.
-func TestAddWhileRunning(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.Start()
-	defer cron.Stop()
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test timing with Entries.
-func TestSnapshotEntries(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddFunc("", "@every 2s", func() { wg.Done() })
-	cron.Start()
-	defer cron.Stop()
-
-	// Cron should fire in 2 seconds. After 1 second, call Entries.
-	select {
-	case <-time.After(ONE_SECOND):
-		cron.Entries()
-	}
-
-	// Even though Entries was called, the cron should fire at the 2 second mark.
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-
-}
-
-// Test that the entries are correctly sorted.
-// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
-// that the immediate entry runs immediately.
-// Also: Test that multiple jobs run in the same instant.
-func TestMultipleEntries(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test running the same job twice.
-func TestRunningJobTwice(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
-	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(2 * ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-func TestRunningMultipleSchedules(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(2)
-
-	cron := New()
-	cron.AddFunc("", "0 0 0 1 1 ?", func() {})
-	cron.AddFunc("", "0 0 0 31 12 ?", func() {})
-	cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
-	cron.Schedule("", "", Every(time.Minute), FuncJob(func() {}))
-	cron.Schedule("", "", Every(time.Second), FuncJob(func() { wg.Done() }))
-	cron.Schedule("", "", Every(time.Hour), FuncJob(func() {}))
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(2 * ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-// Test that the cron is run in the local time zone (as opposed to UTC).
-func TestLocalTimezone(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	now := time.Now().Local()
-	spec := fmt.Sprintf("%d %d %d %d %d ?",
-		now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
-
-	cron := New()
-	cron.AddFunc("", spec, func() { wg.Done() })
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-}
-
-type testJob struct {
-	wg   *sync.WaitGroup
-	name string
-}
-
-func (t testJob) Run() {
-	t.wg.Done()
-}
-
-// Simple test using Runnables.
-func TestJob(t *testing.T) {
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	cron := New()
-	cron.AddJob("", "0 0 0 30 Feb ?", testJob{wg, "job0"})
-	cron.AddJob("", "0 0 0 1 1 ?", testJob{wg, "job1"})
-	cron.AddJob("", "* * * * * ?", testJob{wg, "job2"})
-	cron.AddJob("", "1 0 0 1 1 ?", testJob{wg, "job3"})
-	cron.Schedule("", "", Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
-	cron.Schedule("", "", Every(5*time.Minute), testJob{wg, "job5"})
-
-	cron.Start()
-	defer cron.Stop()
-
-	select {
-	case <-time.After(ONE_SECOND):
-		t.FailNow()
-	case <-wait(wg):
-	}
-
-	// Ensure the entries are in the right order.
-	expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
-
-	var actuals []string
-	for _, entry := range cron.Entries() {
-		actuals = append(actuals, entry.Job.(testJob).name)
-	}
-
-	for i, expected := range expecteds {
-		if actuals[i] != expected {
-			t.Errorf("Jobs not in the right order.  (expected) %s != %s (actual)", expecteds, actuals)
-			t.FailNow()
-		}
-	}
-}
-
-func wait(wg *sync.WaitGroup) chan bool {
-	ch := make(chan bool)
-	go func() {
-		wg.Wait()
-		ch <- true
-	}()
-	return ch
-}
-
-func stop(cron *Cron) chan bool {
-	ch := make(chan bool)
-	go func() {
-		cron.Stop()
-		ch <- true
-	}()
-	return ch
-}

+ 0 - 129
modules/cron/doc.go

@@ -1,129 +0,0 @@
-/*
-Package cron implements a cron spec parser and job runner.
-
-Usage
-
-Callers may register Funcs to be invoked on a given schedule.  Cron will run
-them in their own goroutines.
-
-	c := cron.New()
-	c.AddFunc("Every hour on the half hour","0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
-	c.AddFunc("Every hour","@hourly", func() { fmt.Println("Every hour") })
-	c.AddFunc("Every hour and a half","@every 1h30m", func() { fmt.Println("Every hour thirty") })
-	c.Start()
-	..
-	// Funcs are invoked in their own goroutine, asynchronously.
-	...
-	// Funcs may also be added to a running Cron
-	c.AddFunc("@daily", func() { fmt.Println("Every day") })
-	..
-	// Inspect the cron job entries' next and previous run times.
-	inspect(c.Entries())
-	..
-	c.Stop()  // Stop the scheduler (does not stop any jobs already running).
-
-CRON Expression Format
-
-A cron expression represents a set of times, using 6 space-separated fields.
-
-	Field name   | Mandatory? | Allowed values  | Allowed special characters
-	----------   | ---------- | --------------  | --------------------------
-	Seconds      | Yes        | 0-59            | * / , -
-	Minutes      | Yes        | 0-59            | * / , -
-	Hours        | Yes        | 0-23            | * / , -
-	Day of month | Yes        | 1-31            | * / , - ?
-	Month        | Yes        | 1-12 or JAN-DEC | * / , -
-	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?
-
-Note: Month and Day-of-week field values are case insensitive.  "SUN", "Sun",
-and "sun" are equally accepted.
-
-Special Characters
-
-Asterisk ( * )
-
-The asterisk indicates that the cron expression will match for all values of the
-field; e.g., using an asterisk in the 5th field (month) would indicate every
-month.
-
-Slash ( / )
-
-Slashes are used to describe increments of ranges. For example 3-59/15 in the
-1st field (minutes) would indicate the 3rd minute of the hour and every 15
-minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
-that is, an increment over the largest possible range of the field.  The form
-"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
-increment until the end of that specific range.  It does not wrap around.
-
-Comma ( , )
-
-Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
-the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
-
-Hyphen ( - )
-
-Hyphens are used to define ranges. For example, 9-17 would indicate every
-hour between 9am and 5pm inclusive.
-
-Question mark ( ? )
-
-Question mark may be used instead of '*' for leaving either day-of-month or
-day-of-week blank.
-
-Predefined schedules
-
-You may use one of several pre-defined schedules in place of a cron expression.
-
-	Entry                  | Description                                | Equivalent To
-	-----                  | -----------                                | -------------
-	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 0 1 1 *
-	@monthly               | Run once a month, midnight, first of month | 0 0 0 1 * *
-	@weekly                | Run once a week, midnight on Sunday        | 0 0 0 * * 0
-	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 0 * * *
-	@hourly                | Run once an hour, beginning of hour        | 0 0 * * * *
-
-Intervals
-
-You may also schedule a job to execute at fixed intervals.  This is supported by
-formatting the cron spec like this:
-
-    @every <duration>
-
-where "duration" is a string accepted by time.ParseDuration
-(http://golang.org/pkg/time/#ParseDuration).
-
-For example, "@every 1h30m10s" would indicate a schedule that activates every
-1 hour, 30 minutes, 10 seconds.
-
-Note: The interval does not take the job runtime into account.  For example,
-if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
-it will have only 2 minutes of idle time between each run.
-
-Time zones
-
-All interpretation and scheduling is done in the machine's local time zone (as
-provided by the Go time package (http://www.golang.org/pkg/time).
-
-Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
-not be run!
-
-Thread safety
-
-Since the Cron service runs concurrently with the calling code, some amount of
-care must be taken to ensure proper synchronization.
-
-All cron methods are designed to be correctly synchronized as long as the caller
-ensures that invocations have a clear happens-before ordering between them.
-
-Implementation
-
-Cron entries are stored in an array, sorted by their next activation time.  Cron
-sleeps until the next job is due to be run.
-
-Upon waking:
- - it runs each entry that is active on that second
- - it calculates the next run times for the jobs that were run
- - it re-sorts the array of entries by next activation time.
- - it goes to sleep until the soonest job.
-*/
-package cron

+ 0 - 231
modules/cron/parser.go

@@ -1,231 +0,0 @@
-package cron
-
-import (
-	"fmt"
-	"log"
-	"math"
-	"strconv"
-	"strings"
-	"time"
-)
-
-// Parse returns a new crontab schedule representing the given spec.
-// It returns a descriptive error if the spec is not valid.
-//
-// It accepts
-//   - Full crontab specs, e.g. "* * * * * ?"
-//   - Descriptors, e.g. "@midnight", "@every 1h30m"
-func Parse(spec string) (_ Schedule, err error) {
-	// Convert panics into errors
-	defer func() {
-		if recovered := recover(); recovered != nil {
-			err = fmt.Errorf("%v", recovered)
-		}
-	}()
-
-	if spec[0] == '@' {
-		return parseDescriptor(spec), nil
-	}
-
-	// Split on whitespace.  We require 5 or 6 fields.
-	// (second) (minute) (hour) (day of month) (month) (day of week, optional)
-	fields := strings.Fields(spec)
-	if len(fields) != 5 && len(fields) != 6 {
-		log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
-	}
-
-	// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
-	if len(fields) == 5 {
-		fields = append(fields, "*")
-	}
-
-	schedule := &SpecSchedule{
-		Second: getField(fields[0], seconds),
-		Minute: getField(fields[1], minutes),
-		Hour:   getField(fields[2], hours),
-		Dom:    getField(fields[3], dom),
-		Month:  getField(fields[4], months),
-		Dow:    getField(fields[5], dow),
-	}
-
-	return schedule, nil
-}
-
-// getField returns an Int with the bits set representing all of the times that
-// the field represents.  A "field" is a comma-separated list of "ranges".
-func getField(field string, r bounds) uint64 {
-	// list = range {"," range}
-	var bits uint64
-	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
-	for _, expr := range ranges {
-		bits |= getRange(expr, r)
-	}
-	return bits
-}
-
-// getRange returns the bits indicated by the given expression:
-//   number | number "-" number [ "/" number ]
-func getRange(expr string, r bounds) uint64 {
-
-	var (
-		start, end, step uint
-		rangeAndStep     = strings.Split(expr, "/")
-		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
-		singleDigit      = len(lowAndHigh) == 1
-	)
-
-	var extra_star uint64
-	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
-		start = r.min
-		end = r.max
-		extra_star = starBit
-	} else {
-		start = parseIntOrName(lowAndHigh[0], r.names)
-		switch len(lowAndHigh) {
-		case 1:
-			end = start
-		case 2:
-			end = parseIntOrName(lowAndHigh[1], r.names)
-		default:
-			log.Panicf("Too many hyphens: %s", expr)
-		}
-	}
-
-	switch len(rangeAndStep) {
-	case 1:
-		step = 1
-	case 2:
-		step = mustParseInt(rangeAndStep[1])
-
-		// Special handling: "N/step" means "N-max/step".
-		if singleDigit {
-			end = r.max
-		}
-	default:
-		log.Panicf("Too many slashes: %s", expr)
-	}
-
-	if start < r.min {
-		log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
-	}
-	if end > r.max {
-		log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
-	}
-	if start > end {
-		log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
-	}
-
-	return getBits(start, end, step) | extra_star
-}
-
-// parseIntOrName returns the (possibly-named) integer contained in expr.
-func parseIntOrName(expr string, names map[string]uint) uint {
-	if names != nil {
-		if namedInt, ok := names[strings.ToLower(expr)]; ok {
-			return namedInt
-		}
-	}
-	return mustParseInt(expr)
-}
-
-// mustParseInt parses the given expression as an int or panics.
-func mustParseInt(expr string) uint {
-	num, err := strconv.Atoi(expr)
-	if err != nil {
-		log.Panicf("Failed to parse int from %s: %s", expr, err)
-	}
-	if num < 0 {
-		log.Panicf("Negative number (%d) not allowed: %s", num, expr)
-	}
-
-	return uint(num)
-}
-
-// getBits sets all bits in the range [min, max], modulo the given step size.
-func getBits(min, max, step uint) uint64 {
-	var bits uint64
-
-	// If step is 1, use shifts.
-	if step == 1 {
-		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
-	}
-
-	// Else, use a simple loop.
-	for i := min; i <= max; i += step {
-		bits |= 1 << i
-	}
-	return bits
-}
-
-// all returns all bits within the given bounds.  (plus the star bit)
-func all(r bounds) uint64 {
-	return getBits(r.min, r.max, 1) | starBit
-}
-
-// parseDescriptor returns a pre-defined schedule for the expression, or panics
-// if none matches.
-func parseDescriptor(spec string) Schedule {
-	switch spec {
-	case "@yearly", "@annually":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    1 << dom.min,
-			Month:  1 << months.min,
-			Dow:    all(dow),
-		}
-
-	case "@monthly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    1 << dom.min,
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-
-	case "@weekly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    1 << dow.min,
-		}
-
-	case "@daily", "@midnight":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   1 << hours.min,
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-
-	case "@hourly":
-		return &SpecSchedule{
-			Second: 1 << seconds.min,
-			Minute: 1 << minutes.min,
-			Hour:   all(hours),
-			Dom:    all(dom),
-			Month:  all(months),
-			Dow:    all(dow),
-		}
-	}
-
-	const every = "@every "
-	if strings.HasPrefix(spec, every) {
-		duration, err := time.ParseDuration(spec[len(every):])
-		if err != nil {
-			log.Panicf("Failed to parse duration %s: %s", spec, err)
-		}
-		return Every(duration)
-	}
-
-	log.Panicf("Unrecognized descriptor: %s", spec)
-	return nil
-}

+ 0 - 117
modules/cron/parser_test.go

@@ -1,117 +0,0 @@
-package cron
-
-import (
-	"reflect"
-	"testing"
-	"time"
-)
-
-func TestRange(t *testing.T) {
-	ranges := []struct {
-		expr     string
-		min, max uint
-		expected uint64
-	}{
-		{"5", 0, 7, 1 << 5},
-		{"0", 0, 7, 1 << 0},
-		{"7", 0, 7, 1 << 7},
-
-		{"5-5", 0, 7, 1 << 5},
-		{"5-6", 0, 7, 1<<5 | 1<<6},
-		{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7},
-
-		{"5-6/2", 0, 7, 1 << 5},
-		{"5-7/2", 0, 7, 1<<5 | 1<<7},
-		{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7},
-
-		{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit},
-		{"*/2", 1, 3, 1<<1 | 1<<3 | starBit},
-	}
-
-	for _, c := range ranges {
-		actual := getRange(c.expr, bounds{c.min, c.max, nil})
-		if actual != c.expected {
-			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
-		}
-	}
-}
-
-func TestField(t *testing.T) {
-	fields := []struct {
-		expr     string
-		min, max uint
-		expected uint64
-	}{
-		{"5", 1, 7, 1 << 5},
-		{"5,6", 1, 7, 1<<5 | 1<<6},
-		{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
-		{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
-	}
-
-	for _, c := range fields {
-		actual := getField(c.expr, bounds{c.min, c.max, nil})
-		if actual != c.expected {
-			t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
-		}
-	}
-}
-
-func TestBits(t *testing.T) {
-	allBits := []struct {
-		r        bounds
-		expected uint64
-	}{
-		{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
-		{hours, 0xffffff},            // 0-23: 24 ones
-		{dom, 0xfffffffe},            // 1-31: 31 ones, 1 zero
-		{months, 0x1ffe},             // 1-12: 12 ones, 1 zero
-		{dow, 0x7f},                  // 0-6: 7 ones
-	}
-
-	for _, c := range allBits {
-		actual := all(c.r) // all() adds the starBit, so compensate for that..
-		if c.expected|starBit != actual {
-			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
-				c.r.min, c.r.max, 1, c.expected|starBit, actual)
-		}
-	}
-
-	bits := []struct {
-		min, max, step uint
-		expected       uint64
-	}{
-
-		{0, 0, 1, 0x1},
-		{1, 1, 1, 0x2},
-		{1, 5, 2, 0x2a}, // 101010
-		{1, 4, 2, 0xa},  // 1010
-	}
-
-	for _, c := range bits {
-		actual := getBits(c.min, c.max, c.step)
-		if c.expected != actual {
-			t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
-				c.min, c.max, c.step, c.expected, actual)
-		}
-	}
-}
-
-func TestSpecSchedule(t *testing.T) {
-	entries := []struct {
-		expr     string
-		expected Schedule
-	}{
-		{"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}},
-		{"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}},
-	}
-
-	for _, c := range entries {
-		actual, err := Parse(c.expr)
-		if err != nil {
-			t.Error(err)
-		}
-		if !reflect.DeepEqual(actual, c.expected) {
-			t.Errorf("%s => (expected) %v != %v (actual)", c.expr, c.expected, actual)
-		}
-	}
-}

+ 0 - 161
modules/cron/spec.go

@@ -1,161 +0,0 @@
-package cron
-
-import (
-	"time"
-)
-
-// SpecSchedule specifies a duty cycle (to the second granularity), based on a
-// traditional crontab specification. It is computed initially and stored as bit sets.
-type SpecSchedule struct {
-	Second, Minute, Hour, Dom, Month, Dow uint64
-}
-
-// bounds provides a range of acceptable values (plus a map of name to value).
-type bounds struct {
-	min, max uint
-	names    map[string]uint
-}
-
-// The bounds for each field.
-var (
-	seconds = bounds{0, 59, nil}
-	minutes = bounds{0, 59, nil}
-	hours   = bounds{0, 23, nil}
-	dom     = bounds{1, 31, nil}
-	months  = bounds{1, 12, map[string]uint{
-		"jan": 1,
-		"feb": 2,
-		"mar": 3,
-		"apr": 4,
-		"may": 5,
-		"jun": 6,
-		"jul": 7,
-		"aug": 8,
-		"sep": 9,
-		"oct": 10,
-		"nov": 11,
-		"dec": 12,
-	}}
-	dow = bounds{0, 6, map[string]uint{
-		"sun": 0,
-		"mon": 1,
-		"tue": 2,
-		"wed": 3,
-		"thu": 4,
-		"fri": 5,
-		"sat": 6,
-	}}
-)
-
-const (
-	// Set the top bit if a star was included in the expression.
-	starBit = 1 << 63
-)
-
-// Next returns the next time this schedule is activated, greater than the given
-// time.  If no time can be found to satisfy the schedule, return the zero time.
-func (s *SpecSchedule) Next(t time.Time) time.Time {
-	// General approach:
-	// For Month, Day, Hour, Minute, Second:
-	// Check if the time value matches.  If yes, continue to the next field.
-	// If the field doesn't match the schedule, then increment the field until it matches.
-	// While incrementing the field, a wrap-around brings it back to the beginning
-	// of the field list (since it is necessary to re-verify previous field
-	// values)
-
-	// Start at the earliest possible time (the upcoming second).
-	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
-
-	// This flag indicates whether a field has been incremented.
-	added := false
-
-	// If no time is found within five years, return zero.
-	yearLimit := t.Year() + 5
-
-WRAP:
-	if t.Year() > yearLimit {
-		return time.Time{}
-	}
-
-	// Find the first applicable month.
-	// If it's this month, then do nothing.
-	for 1<<uint(t.Month())&s.Month == 0 {
-		// If we have to add a month, reset the other parts to 0.
-		if !added {
-			added = true
-			// Otherwise, set the date at the beginning (since the current time is irrelevant).
-			t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
-		}
-		t = t.AddDate(0, 1, 0)
-
-		// Wrapped around.
-		if t.Month() == time.January {
-			goto WRAP
-		}
-	}
-
-	// Now get a day in that month.
-	for !dayMatches(s, t) {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
-		}
-		t = t.AddDate(0, 0, 1)
-
-		if t.Day() == 1 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Hour())&s.Hour == 0 {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
-		}
-		t = t.Add(1 * time.Hour)
-
-		if t.Hour() == 0 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Minute())&s.Minute == 0 {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, t.Location())
-		}
-		t = t.Add(1 * time.Minute)
-
-		if t.Minute() == 0 {
-			goto WRAP
-		}
-	}
-
-	for 1<<uint(t.Second())&s.Second == 0 {
-		if !added {
-			added = true
-			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, t.Location())
-		}
-		t = t.Add(1 * time.Second)
-
-		if t.Second() == 0 {
-			goto WRAP
-		}
-	}
-
-	return t
-}
-
-// dayMatches returns true if the schedule's day-of-week and day-of-month
-// restrictions are satisfied by the given time.
-func dayMatches(s *SpecSchedule, t time.Time) bool {
-	var (
-		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
-		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
-	)
-
-	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
-		return domMatch && dowMatch
-	}
-	return domMatch || dowMatch
-}

+ 0 - 173
modules/cron/spec_test.go

@@ -1,173 +0,0 @@
-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},
-		{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
-	}
-
-	for _, test := range tests {
-		sched, err := Parse(test.spec)
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
-		expected := getTime(test.time)
-		if test.expected && expected != actual || !test.expected && expected == actual {
-			t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
-				test.spec, test.time, expected, actual)
-		}
-	}
-}
-
-func TestNext(t *testing.T) {
-	runs := []struct {
-		time, spec string
-		expected   string
-	}{
-		// Simple cases
-		{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-		{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
-
-		// Wrap around hours
-		{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
-
-		// Wrap around days
-		{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
-		{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
-
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
-		{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
-
-		// Wrap around months
-		{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
-
-		// Wrap around years
-		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
-		{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
-
-		// Wrap around minute, hour, day, month, and year
-		{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
-
-		// Leap year
-		{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
-
-		// Daylight savings time EST -> EDT
-		{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
-
-		// Daylight savings time EDT -> EST
-		{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
-		{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
-
-		// Unsatisfiable
-		{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
-		{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
-	}
-
-	for _, c := range runs {
-		sched, err := Parse(c.spec)
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		actual := sched.Next(getTime(c.time))
-		expected := getTime(c.expected)
-		if !actual.Equal(expected) {
-			t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
-		}
-	}
-}
-
-func TestErrors(t *testing.T) {
-	invalidSpecs := []string{
-		"xyz",
-		"60 0 * * *",
-		"0 60 * * *",
-		"0 0 * * XYZ",
-	}
-	for _, spec := range invalidSpecs {
-		_, err := Parse(spec)
-		if err == nil {
-			t.Error("expected an error parsing: ", spec)
-		}
-	}
-}
-
-func getTime(value string) time.Time {
-	if value == "" {
-		return time.Time{}
-	}
-	t, err := time.Parse("Mon Jan 2 15:04 2006", value)
-	if err != nil {
-		t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
-		if err != nil {
-			t, err = time.Parse("2006-01-02T15:04:05-0700", value)
-			if err != nil {
-				panic(err)
-			}
-			// Daylight savings time tests require location
-			if ny, err := time.LoadLocation("America/New_York"); err == nil {
-				t = t.In(ny)
-			}
-		}
-	}
-
-	return t
-}

+ 1 - 1
routers/admin/admin.go

@@ -14,8 +14,8 @@ import (
 	"gopkg.in/macaron.v1"
 
 	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/models/cron"
 	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/cron"
 	"github.com/gogits/gogs/modules/middleware"
 	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"

+ 1 - 1
routers/install.go

@@ -20,9 +20,9 @@ import (
 	"github.com/gogits/git-module"
 
 	"github.com/gogits/gogs/models"
-	"github.com/gogits/gogs/models/cron"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/cron"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/mailer"
 	"github.com/gogits/gogs/modules/middleware"