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

Issue:
[BUG] - NewAtTimes Function Modifies Original Slice
### Describe the bug

In my attempts to parse a gocron.AtTimes back to a time.Time I discovered a strange behavior.
My current attempts consists of this part of the function, that takes the gocron.AtTimes and converts it back formated time Strings:
```go
func reverseParseTimes(atTimes gocron.AtTimes) []string {
	var times []string
	unparsedTimesSlice := atTimes()
	for _, atTime := range unparsedTimesSlice {
		location, _ := time.LoadLocation("Local")
		timeStr := gocron.TimeFromAtTime(atTime, location).Format("15:04")
		times = append(times, timeStr)
	}
	return times
}
```

When I execute this function multiple times with the same input atTimes then the atTimes seems to be modified and appended by the first entry each time.
These are the logger prints after each execution:
```bash
{"level":"info","msg":"Times: [0x863200 0x863200 0x863200]","time":"2024-12-18T10:13:51+01:00"}
{"level":"info","msg":"Parsed times: [09:59 09:30 09:30 09:30]","time":"2024-12-18T10:13:51+01:00"}
{"level":"info","msg":"Times: [0x863200 0x863200 0x863200 0x863200 0x863200]","time":"2024-12-18T10:13:54+01:00"}
{"level":"info","msg":"Parsed times: [09:59 09:30 09:30 09:30 09:30 09:30]","time":"2024-12-18T10:13:54+01:00"}
{"level":"info","msg":"Times: [0x863200 0x863200 0x863200 0x863200 0x863200 0x863200 0x863200]","time":"2024-12-18T10:14:05+01:00"}
{"level":"info","msg":"Parsed times: [09:59 09:30 09:30 09:30 09:30 09:30 09:30 09:30]","time":"2024-12-18T10:14:05+01:00"}
```
The original atTimes was only 9:30 and 9:59, therefore I expect only `[9:30 9:59]` as a return.

### To Reproduce

Steps to reproduce the behavior:

1. Create a `gocron.AtTimes` instance with two `AtTime` values, e.g., `9:30`and `9:59`.
2. Call the `reverseParseTimes` function with the created `gocron.AtTimes` instance.
3. Log the output of the `reverseParseTimes` function.
4. Repeat step 2 and 3 multiple times.

### Version
I used `v2.14.0`

### Expected behavior

I expect the `reverseParseTimes` function to consistently return the same parsed times without appending duplicate entries. Specifically, the expected output should be `[9:30 9:59]` for each execution.

### Additional context
I tried to look a bit into where the problem might be and discovered that changing the NewAtTimes function seems to fix my issue (although I am not sure if it creates other bugs, it was just a crude attempt).
Original `NewAtTimes` function:
```go
func NewAtTimes(atTime AtTime, atTimes ...AtTime) AtTimes {
    return func() []AtTime {
        atTimes = append(atTimes, atTime)
        return atTimes
    }
}
```
Modified `NewAtTimes` function:
```go
func NewAtTimes(atTime AtTime, atTimes ...AtTime) AtTimes {
    return func() []AtTime {
        return append([]AtTime{atTime}, atTimes...)
    }
}
```

This was a fix proposed by Copilot so I would not trust it completely and I am not entirely sure what is different in the modified function. 
But I think the original function seems to modify the original atTimes slice by appending atTime to it each time the function is called. The second implementation creates a new slice each time, therefore preserving the original atTimes slice?

I observe the expected behavior with the modified `NewAtTimes` function:
```go
{"level":"info","msg":"Times: [0x863220 0x863220]","time":"2024-12-18T10:14:34+01:00"}
{"level":"info","msg":"Parsed times: [09:30 09:59]","time":"2024-12-18T10:14:34+01:00"}
{"level":"info","msg":"Times: [0x863220 0x863220]","time":"2024-12-18T10:14:35+01:00"}
{"level":"info","msg":"Parsed times: [09:30 09:59]","time":"2024-12-18T10:14:35+01:00"}
{"level":"info","msg":"Times: [0x863220 0x863220]","time":"2024-12-18T10:14:36+01:00"}
{"level":"info","msg":"Parsed times: [09:30 09:59]","time":"2024-12-18T10:14:36+01:00"}
```

Pull request description:
creating a new slice in several job options because appending modifies original
### What does this do?
The original slices passed into the functions were being modified, leading to undesirable results!

### 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 #807

### List any changes that modify/break current functionality


### Have you included tests for your changes?

TODO - add tests that fail before the changes, and pass after. This should be doable by calling the function more than once.

### 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 890cc12..2ea8e91 100644
--- a/job.go
+++ b/job.go
@@ -311,8 +311,7 @@ type Weekdays func() []time.Weekday
 // NewWeekdays provide the days of the week the job should run.
 func NewWeekdays(weekday time.Weekday, weekdays ...time.Weekday) Weekdays {
 	return func() []time.Weekday {
-		weekdays = append(weekdays, weekday)
-		return weekdays
+		return append([]time.Weekday{weekday}, weekdays...)
 	}
 }
 
@@ -400,8 +399,7 @@ type DaysOfTheMonth func() days
 //	-5 == 5 days before the end of the month.
 func NewDaysOfTheMonth(day int, moreDays ...int) DaysOfTheMonth {
 	return func() days {
-		moreDays = append(moreDays, day)
-		return moreDays
+		return append([]int{day}, moreDays...)
 	}
 }
 
@@ -439,8 +437,7 @@ type AtTimes func() []AtTime
 // the job should be run
 func NewAtTimes(atTime AtTime, atTimes ...AtTime) AtTimes {
 	return func() []AtTime {
-		atTimes = append(atTimes, atTime)
-		return atTimes
+		return append([]AtTime{atTime}, atTimes...)
 	}
 }
 


Test patch:
diff --git a/job_test.go b/job_test.go
index d60c2a4..b729486 100644
--- a/job_test.go
+++ b/job_test.go
@@ -724,3 +724,59 @@ func TestTimeFromAtTime(t *testing.T) {
 		})
 	}
 }
+
+func TestNewAtTimes(t *testing.T) {
+	at := NewAtTimes(
+		NewAtTime(1, 1, 1),
+		NewAtTime(2, 2, 2),
+	)
+
+	var times []string
+	for _, att := range at() {
+		timeStr := TimeFromAtTime(att, time.UTC).Format("15:04")
+		times = append(times, timeStr)
+	}
+
+	var timesAgain []string
+	for _, att := range at() {
+		timeStr := TimeFromAtTime(att, time.UTC).Format("15:04")
+		timesAgain = append(timesAgain, timeStr)
+	}
+
+	assert.Equal(t, times, timesAgain)
+}
+
+func TestNewWeekdays(t *testing.T) {
+	wd := NewWeekdays(
+		time.Monday,
+		time.Tuesday,
+	)
+
+	var dayStrings []string
+	for _, w := range wd() {
+		dayStrings = append(dayStrings, w.String())
+	}
+
+	var dayStringsAgain []string
+	for _, w := range wd() {
+		dayStringsAgain = append(dayStringsAgain, w.String())
+	}
+
+	assert.Equal(t, dayStrings, dayStringsAgain)
+}
+
+func TestNewDaysOfTheMonth(t *testing.T) {
+	dom := NewDaysOfTheMonth(1, 2, 3)
+
+	var domInts []int
+	for _, d := range dom() {
+		domInts = append(domInts, d)
+	}
+
+	var domIntsAgain []int
+	for _, d := range dom() {
+		domIntsAgain = append(domIntsAgain, d)
+	}
+
+	assert.Equal(t, domInts, domIntsAgain)
+}