Repository: go-co-op/gocron
Pull request #162 (2021-04-15 14:43:37)

add WaitForSchedule(s) funcs
### What does this do?
- adds `WaitForSchedule` and `WaitForSchedules` to allow setting per job or for all jobs to wait for the first scheduled run rather than running immediately
- un-exports the job's `NewJob` method. There was no use for it to be exported as we don't accept a newly created job struct in any scheduler methods. alters the signature to handle the wait for interval logic

### Which issue(s) does this PR fix/relate to?
<!--- Put `Resolves #XXX` here to auto-close the issue that your PR fixes (if such) --->
#152 

### List any changes that modify/break current functionality


### Have you included tests for your changes?


### Did you document any new/modified functionality?

- [x] Updated `example_test.go`
- [ ] Updated `README.md`

### Notes



Patch:
diff --git a/job.go b/job.go
index ff3886b..06bd147 100644
--- a/job.go
+++ b/job.go
@@ -58,8 +58,8 @@ const (
 	singletonMode
 )
 
-// NewJob creates a new Job with the provided interval
-func NewJob(interval int) *Job {
+// newJob creates a new Job with the provided interval
+func newJob(interval int, startImmediately bool) *Job {
 	ctx, cancel := context.WithCancel(context.Background())
 	return &Job{
 		interval: interval,
@@ -71,7 +71,7 @@ func NewJob(interval int) *Job {
 			cancel: cancel,
 		},
 		tags:              []string{},
-		startsImmediately: true,
+		startsImmediately: startImmediately,
 	}
 }
 
diff --git a/scheduler.go b/scheduler.go
index b436a13..668aae2 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -32,7 +32,8 @@ type Scheduler struct {
 
 	tags map[string]struct{} // for storing tags when unique tags is set
 
-	updateJob bool // so the scheduler knows to create a new job or update the current
+	updateJob       bool // so the scheduler knows to create a new job or update the current
+	waitForInterval bool // defaults jobs to waiting for first interval to start
 }
 
 // NewScheduler creates a new Scheduler
@@ -348,7 +349,7 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 	switch interval := interval.(type) {
 	case int:
 		if !s.updateJob {
-			job = NewJob(interval)
+			job = s.newJob(interval)
 		} else {
 			job.interval = interval
 		}
@@ -357,7 +358,7 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 		}
 	case time.Duration:
 		if !s.updateJob {
-			job = NewJob(0)
+			job = s.newJob(0)
 		} else {
 			job.interval = 0
 		}
@@ -365,7 +366,7 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 		job.setUnit(duration)
 	case string:
 		if !s.updateJob {
-			job = NewJob(0)
+			job = s.newJob(0)
 		} else {
 			job.interval = 0
 		}
@@ -377,7 +378,7 @@ func (s *Scheduler) Every(interval interface{}) *Scheduler {
 		job.setUnit(duration)
 	default:
 		if !s.updateJob {
-			job = NewJob(0)
+			job = s.newJob(0)
 		} else {
 			job.interval = 0
 		}
@@ -851,7 +852,7 @@ func (s *Scheduler) CronWithSeconds(cronExpression string) *Scheduler {
 }
 
 func (s *Scheduler) cron(cronExpression string, withSeconds bool) *Scheduler {
-	job := NewJob(0)
+	job := s.newJob(0)
 
 	withLocation := fmt.Sprintf("CRON_TZ=%s %s", s.location.String(), cronExpression)
 
@@ -878,3 +879,23 @@ func (s *Scheduler) cron(cronExpression string, withSeconds bool) *Scheduler {
 	s.setJobs(append(s.Jobs(), job))
 	return s
 }
+
+func (s *Scheduler) newJob(interval int) *Job {
+	return newJob(interval, !s.waitForInterval)
+}
+
+// WaitForScheduleAll defaults the scheduler to create all
+// new jobs with the WaitForSchedule option as true.
+// The jobs will not start immediately but rather will
+// wait until their first scheduled interval.
+func (s *Scheduler) WaitForScheduleAll() {
+	s.waitForInterval = true
+}
+
+// WaitForSchedule sets the job to not start immediately
+// but rather wait until the first scheduled interval.
+func (s *Scheduler) WaitForSchedule() *Scheduler {
+	job := s.getCurrentJob()
+	job.startsImmediately = false
+	return s
+}


Test patch:
diff --git a/example_test.go b/example_test.go
index 010d6f5..10c3abf 100644
--- a/example_test.go
+++ b/example_test.go
@@ -596,7 +596,7 @@ func ExampleScheduler_Tuesday() {
 
 func ExampleScheduler_Update() {
 	s := gocron.NewScheduler(time.UTC)
-	j, _ := s.Every("1s").Do(func() {})
+	j, _ := s.Every("1s").Do(task)
 	s.StartAsync()
 
 	time.Sleep(10 * time.Second)
@@ -606,6 +606,27 @@ func ExampleScheduler_Update() {
 	_, _ = s.Job(j).Every(1).Day().At("02:00").Update()
 }
 
+func ExampleScheduler_WaitForSchedule() {
+	s := gocron.NewScheduler(time.UTC)
+
+	// job will run 5 minutes from the scheduler starting
+	_, _ = s.Every("5m").WaitForSchedule().Do(task)
+
+	// job will run immediately and 5 minutes from the scheduler starting
+	_, _ = s.Every("5m").Do(task)
+	s.StartAsync()
+}
+
+func ExampleScheduler_WaitForScheduleAll() {
+	s := gocron.NewScheduler(time.UTC)
+	s.WaitForScheduleAll()
+
+	// all jobs will run 5 minutes from the scheduler starting
+	_, _ = s.Every("5m").Do(task)
+	_, _ = s.Every("5m").Do(task)
+	s.StartAsync()
+}
+
 func ExampleScheduler_Wednesday() {
 	s := gocron.NewScheduler(time.UTC)
 	j, _ := s.Every(1).Day().Wednesday().Do(task)
diff --git a/scheduler_test.go b/scheduler_test.go
index 24d4b15..b8bed71 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -1296,3 +1296,40 @@ func TestScheduler_CronWithSeconds(t *testing.T) {
 		assert.EqualError(t, err, wrapOrError(ErrInvalidIntervalUnitsSelection, ErrWeekdayNotSupported).Error())
 	})
 }
+
+func TestScheduler_WaitForSchedule(t *testing.T) {
+	s := NewScheduler(time.UTC)
+
+	var counterMutex sync.RWMutex
+	counter := 0
+
+	_, err := s.Every("100ms").WaitForSchedule().Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++ })
+	require.NoError(t, err)
+	s.StartAsync()
+
+	time.Sleep(350 * time.Millisecond)
+	s.Stop()
+
+	counterMutex.RLock()
+	defer counterMutex.RUnlock()
+	assert.Equal(t, 3, counter)
+}
+
+func TestScheduler_WaitForSchedules(t *testing.T) {
+	s := NewScheduler(time.UTC)
+	s.WaitForScheduleAll()
+
+	var counterMutex sync.RWMutex
+	counter := 0
+
+	_, err := s.Every("100ms").Do(func() { counterMutex.Lock(); defer counterMutex.Unlock(); counter++ })
+	require.NoError(t, err)
+	s.StartAsync()
+
+	time.Sleep(350 * time.Millisecond)
+	s.Stop()
+
+	counterMutex.RLock()
+	defer counterMutex.RUnlock()
+	assert.Equal(t, 3, counter)
+}