From 6a30d6d8b973cf7da1b01312a364616ee92ecbba Mon Sep 17 00:00:00 2001 From: John Roesler Date: Thu, 9 Oct 2025 21:45:13 -0500 Subject: [PATCH] feat: add WithStartDateTimePast WithStartAt option (#882) --- errors.go | 1 + example_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++ job.go | 20 +++++++++++ scheduler_test.go | 8 +++++ 4 files changed, 113 insertions(+) diff --git a/errors.go b/errors.go index 4a4cb86..f7fbc9a 100644 --- a/errors.go +++ b/errors.go @@ -54,6 +54,7 @@ var ( ErrWithMonitorNil = errors.New("gocron: WithMonitor: monitor must not be nil") ErrWithNameEmpty = errors.New("gocron: WithName: name must not be empty") ErrWithStartDateTimePast = errors.New("gocron: WithStartDateTime: start must not be in the past") + ErrWithStartDateTimePastZero = errors.New("gocron: WithStartDateTime: start must not be zero") ErrWithStopDateTimePast = errors.New("gocron: WithStopDateTime: end must not be in the past") ErrStartTimeLaterThanEndTime = errors.New("gocron: WithStartDateTime: start must not be later than end") ErrStopTimeEarlierThanStartTime = errors.New("gocron: WithStopDateTime: end must not be earlier than start") diff --git a/example_test.go b/example_test.go index bafc4d2..015f3b5 100644 --- a/example_test.go +++ b/example_test.go @@ -1046,6 +1046,90 @@ func ExampleWithStartAt() { // 9999-09-09 09:09:09.000000009 +0000 UTC } +func ExampleWithStartDateTime() { + s, _ := gocron.NewScheduler() + defer func() { _ = s.Shutdown() }() + + start := time.Date(9999, 9, 9, 9, 9, 9, 9, time.UTC) + + j, _ := s.NewJob( + gocron.DurationJob( + time.Second, + ), + gocron.NewTask( + func(one string, two int) { + fmt.Printf("%s, %d", one, two) + }, + "one", 2, + ), + gocron.WithStartAt( + gocron.WithStartDateTime(start), + ), + ) + s.Start() + + next, _ := j.NextRun() + fmt.Println(next) + + _ = s.StopJobs() + // Output: + // 9999-09-09 09:09:09.000000009 +0000 UTC +} + +func ExampleWithStartDateTimePast() { + s, _ := gocron.NewScheduler() + defer func() { _ = s.Shutdown() }() + + start := time.Now().Add(-time.Minute) + + j, _ := s.NewJob( + gocron.DurationJob( + time.Second, + ), + gocron.NewTask( + func(one string, two int) { + fmt.Printf("%s, %d", one, two) + }, + "one", 2, + ), + gocron.WithStartAt( + gocron.WithStartDateTimePast(start), + ), + ) + s.Start() + + time.Sleep(100 * time.Millisecond) + + _, _ = j.NextRun() + + _ = s.StopJobs() +} + +func ExampleWithStartImmediately() { + s, _ := gocron.NewScheduler() + defer func() { _ = s.Shutdown() }() + + j, _ := s.NewJob( + gocron.DurationJob( + time.Second, + ), + gocron.NewTask( + func(one string, two int) { + fmt.Printf("%s, %d", one, two) + }, + "one", 2, + ), + gocron.WithStartAt( + gocron.WithStartImmediately(), + ), + ) + s.Start() + + _, _ = j.NextRun() + + _ = s.StopJobs() +} + func ExampleWithStopTimeout() { _, _ = gocron.NewScheduler( gocron.WithStopTimeout(time.Second * 5), diff --git a/job.go b/job.go index b46680e..80d1310 100644 --- a/job.go +++ b/job.go @@ -718,6 +718,26 @@ func WithStartDateTime(start time.Time) StartAtOption { } } +// WithStartDateTimePast sets the first date & time at which the job should run +// from a time in the past. This is useful when you want to backdate +// the start time of a job to a time in the past, for example +// if you want to start a job from a specific date in the past +// and have it run on its schedule from then. +// The start time can be in the past, but not zero. +// If the start time is in the future, it behaves the same as WithStartDateTime. +func WithStartDateTimePast(start time.Time) StartAtOption { + return func(j *internalJob, _ time.Time) error { + if start.IsZero() { + return ErrWithStartDateTimePastZero + } + if !j.stopTime.IsZero() && j.stopTime.Before(start) { + return ErrStartTimeLaterThanEndTime + } + j.startTime = start + return nil + } +} + // WithStopAt sets the option for stopping the job from running // after the specified time. func WithStopAt(option StopAtOption) JobOption { diff --git a/scheduler_test.go b/scheduler_test.go index d029522..f6b5240 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -1004,6 +1004,14 @@ func TestScheduler_NewJobErrors(t *testing.T) { []JobOption{WithStartAt(WithStartDateTime(time.Now().Add(-time.Second)))}, ErrWithStartDateTimePast, }, + { + "WithStartDateTimePast is zero", + DurationJob( + time.Second, + ), + []JobOption{WithStartAt(WithStartDateTimePast(time.Time{}))}, + ErrWithStartDateTimePastZero, + }, { "WithStartDateTime is later than the end", DurationJob(