Repository: go-co-op/gocron (Go)

Issue:
[BUG] - Weeks does not maintain hour offsets
### Describe the bug

When you use `StartAt` to set offset for a task, if you schedule it using `Every(1).Week()` it will not keep the hour offsets. But if you use `Every(168).Hour()` which is one week, it will keep the offset.

### To Reproduce

Example code:
```go
package main

import (
	"github.com/go-co-op/gocron"
	"log"
	"time"
)

func WeekSchedule(nextTime time.Time) {
	s := gocron.NewScheduler(time.Local)
	_, _ = s.Every(1).Week().StartAt(nextTime).Do(func() { log.Println("Run WeekSchedule") })
	s.StartAsync()
	_, next := s.NextRun()
	log.Println("WeekSchedule Next Run", next.Format(time.RFC3339))
	time.Sleep(15 * time.Second)
	_, next = s.NextRun()
	log.Println("WeekSchedule Next Run", next.Format(time.RFC3339))
}

func HourSchedule(nextTime time.Time) {
	s := gocron.NewScheduler(time.Local)
	_, _ = s.Every(168).Hour().StartAt(nextTime).Do(func() { log.Println("Run HourSchedule") })
	s.StartAsync()
	_, next := s.NextRun()
	log.Println("HourSchedule Next Run", next.Format(time.RFC3339))
	time.Sleep(15 * time.Second)
	_, next = s.NextRun()
	log.Println("HourSchedule Next Run", next.Format(time.RFC3339))
}

func main() {
	k := time.Now().Add(5 * time.Second)
	go WeekSchedule(k)
	go HourSchedule(k)
	time.Sleep(20 * time.Second)
}
```

Output:

```
2022/07/16 18:57:49 HourSchedule Next Run 2022-07-16T18:57:54+08:00
2022/07/16 18:57:49 WeekSchedule Next Run 2022-07-16T18:57:54+08:00
2022/07/16 18:57:54 Run HourSchedule
2022/07/16 18:57:54 Run WeekSchedule
2022/07/16 18:58:04 HourSchedule Next Run 2022-07-23T18:57:54+08:00
2022/07/16 18:58:04 WeekSchedule Next Run 2022-07-23T00:00:00+08:00
```

### Version

fa855edd74858fce4a37ef191b7a6ed3666a7e5e

### Expected behaviour

Both should keep their offset

### Additional context

7 Days also does not maintain hour offsets.

Maybe you can add an option for this, or if this is intended then never mind. 



Discussion:
Relevant section

https://github.com/go-co-op/gocron/blob/9aaee3a5cd42aa105bb396ff829326f479cda2e4/scheduler.go#L327-L331



Patch:
diff --git a/scheduler.go b/scheduler.go
index 758ba1fc..f669b741 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -218,6 +218,12 @@ func (s *Scheduler) scheduleNextRun(job *Job) (bool, nextRun) {
 func (s *Scheduler) durationToNextRun(lastRun time.Time, job *Job) nextRun {
 	// job can be scheduled with .StartAt()
 	if job.getFirstAtTime() == 0 && job.getStartAtTime().After(lastRun) {
+		sa := job.getStartAtTime()
+		job.addAtTime(
+			time.Duration(sa.Hour())*time.Hour +
+				time.Duration(sa.Minute())*time.Minute +
+				time.Duration(sa.Second())*time.Second,
+		)
 		return nextRun{duration: job.getStartAtTime().Sub(s.now()), dateTime: job.getStartAtTime()}
 	}
 


Test patch:
diff --git a/scheduler_test.go b/scheduler_test.go
index 62f22957..ca9757f9 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"go.uber.org/goleak"
 )
 
 var _ TimeWrapper = (*fakeTime)(nil)
@@ -27,8 +26,8 @@ func (f fakeTime) Unix(i int64, i2 int64) time.Time {
 	panic("implement me")
 }
 
-func (f fakeTime) Sleep(duration time.Duration) {
-	panic("implement me")
+func (f fakeTime) Sleep(d time.Duration) {
+	time.Sleep(d)
 }
 
 func task() {
@@ -232,8 +231,6 @@ func TestScheduled(t *testing.T) {
 
 func TestAt(t *testing.T) {
 	t.Run("job scheduled for future hasn't run yet", func(t *testing.T) {
-		defer goleak.VerifyNone(t)
-
 		ft := fakeTime{onNow: func(l *time.Location) time.Time {
 			return time.Date(1970, 1, 1, 12, 0, 0, 0, l)
 		}}
@@ -1086,6 +1083,21 @@ func TestScheduler_StartAt(t *testing.T) {
 		assert.Equal(t, exp, job.NextRun())
 		s.Stop()
 	})
+
+	t.Run("StartAt() with Week() no At()", func(t *testing.T) {
+		s := NewScheduler(time.UTC)
+
+		dt := time.Now().UTC().Add(time.Second)
+		job, err := s.Every(1).Week().StartAt(dt).Do(func() {})
+		require.NoError(t, err)
+
+		s.StartAsync()
+		assert.Equal(t, dt, job.NextRun())
+		time.Sleep(time.Millisecond * 1500)
+		s.Stop()
+
+		assert.Equal(t, dt.Add(time.Hour*168).Truncate(time.Second), job.NextRun())
+	})
 }
 
 func TestScheduler_CalculateNextRun(t *testing.T) {