Make it easier to keep track of upstream changes and bug fixestokarchuk/v1.17
							parent
							
								
									7140dbac95
								
							
						
					
					
						commit
						d8a994ef24
					
				@ -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() | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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) | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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 | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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 | 
					 | 
				
			||||||
@ -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 | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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) | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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 | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -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 | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue