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

Issue:
v2 daily job seems to execute in an infinite loop
### Describe the bug

When starting the scheduler after adding a daily job it seems to execute in an infinite loop. A minimal reproduction of the issue is below. I could be "holding it wrong", so to speak too.

Prior to v2 I was using this a daily job like so:
```
_, err := scheduler.Every(1).Day().At("00:00").Do(func() {
	// Do the thing
})
```

After checking the [godocs for DailyJob](https://pkg.go.dev/github.com/go-co-op/gocron/v2#DailyJob), it looked like this would be the v2 equivalent:

```
_, err = scheduler.NewJob(
	gocron.DailyJob(1, gocron.NewAtTimes(
		gocron.NewAtTime(0, 0, 0),
	)),
	gocron.NewTask(func() {
		// Do the thing
	}),
)
```

### To Reproduce

I have a minimal reproduction of the issue.

`go.mod` entry:
`github.com/go-co-op/gocron/v2 v2.0.0`

Current hashes in `go.sum`:
```
github.com/go-co-op/gocron/v2 v2.0.0 h1:wVB5Bh+665+r/zQ+ErsyUypyPWGozEnNV7EEZ8D0HIU=
github.com/go-co-op/gocron/v2 v2.0.0/go.mod h1:DodDqurAedt8cj/dbFM8obVSgPv0Vch80eF7neNVwmg=
```

```go
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/go-co-op/gocron/v2"
)

func main() {
	scheduler, err := gocron.NewScheduler()
	if err != nil {
		log.Fatalf("Failed to create scheduler: %s", err)
	}

	_, err = scheduler.NewJob(
		gocron.DailyJob(
			1,
			gocron.NewAtTimes(
				gocron.NewAtTime(0, 0, 0),
			),
		),
		gocron.NewTask(func() {
			fmt.Printf("Task ran!\n")
		}),
	)

	if err != nil {
		log.Fatalf("Failed to schedule job: %s", err)
	}

	scheduler.Start()

	time.Sleep(1 * time.Second)

	err = scheduler.StopJobs()
	if err != nil {
		log.Fatalf("Failed to stop jobs: %s", err)
	}
	err = scheduler.Shutdown()
	if err != nil {
		log.Fatalf("Failed to shutdown scheduler: %s", err)
	}
}
```

Running this results in about 45k "Task ran!" messages on my machine.

### Version

> The release version or commit SHA you're using.

v2.0.0 

> Have you tried the latest version?

Yes

### Expected behavior

The daily job should execute daily.

### Additional context

N/A


Pull request description:
daily job next logic failed to consider 1 midnight attime
### What does this do?
Fixes the logic in the daily job's next function to handle a single at time that is midnight. The bug was due to the next day being checked also being at midnight and using time.After, vs. also checking for time equality


### Which issue(s) does this PR fix/relate to?
<!--- Put `Resolves #XXX` here to auto-close the issue that your PR fixes (if such) --->
resolves #634 

### List any changes that modify/break current functionality


### Have you included tests for your changes?


### Did you document any new/modified functionality?

- [ ] Updated `example_test.go`
- [ ] Updated `README.md`

### Notes


Patch:
diff --git a/job.go b/job.go
index 6345ad3..bc2773b 100644
--- a/job.go
+++ b/job.go
@@ -622,12 +622,7 @@ type dailyJob struct {
 }
 
 func (d dailyJob) next(lastRun time.Time) time.Time {
-	next := d.nextDay(lastRun)
-	if !next.IsZero() {
-		return next
-	}
-	startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, lastRun.Nanosecond(), lastRun.Location())
-	return d.nextDay(startNextDay)
+	return d.nextDay(lastRun)
 }
 
 func (d dailyJob) nextDay(lastRun time.Time) time.Time {
@@ -643,6 +638,18 @@ func (d dailyJob) nextDay(lastRun time.Time) time.Time {
 			return atDate
 		}
 	}
+	startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, lastRun.Nanosecond(), lastRun.Location())
+	for _, at := range d.atTimes {
+		// sub the at time hour/min/sec onto the lastRun's values
+		// to use in checks to see if we've got our next run time
+		atDate := time.Date(startNextDay.Year(), startNextDay.Month(), startNextDay.Day(), at.Hour(), at.Minute(), at.Second(), startNextDay.Nanosecond(), startNextDay.Location())
+
+		if !atDate.Before(startNextDay) {
+			// now that we're looking at the next day, it's ok to consider
+			// the same at time that was last run
+			return atDate
+		}
+	}
 	return time.Time{}
 }
 


Test patch:
diff --git a/job_test.go b/job_test.go
index 16d8127..9aeb6be 100644
--- a/job_test.go
+++ b/job_test.go
@@ -50,6 +50,16 @@ func TestDailyJob_next(t *testing.T) {
 		expectedNextRun           time.Time
 		expectedDurationToNextRun time.Duration
 	}{
+		{
+			"daily at midnight",
+			1,
+			[]time.Time{
+				time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC),
+			},
+			time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
+			time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC),
+			24 * time.Hour,
+		},
 		{
 			"daily multiple at times",
 			1,