Repository: go-co-op/gocron
Pull request #168 (2021-04-30 18:29:23)

Fix weekly higher 1
### What does this do?
Fix bug that is described in #165 

### Which issue(s) does this PR fix/relate to?


### 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


joaolcaas: For now, is a draft because the tests are failing at this [test](https://github.com/go-co-op/gocron/blob/master/scheduler_test.go#L680), which I believe might be changed.

Patch:
diff --git a/scheduler.go b/scheduler.go
index 1e68e5b..eae8f87 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -36,6 +36,9 @@ type Scheduler struct {
 	waitForInterval bool // defaults jobs to waiting for first interval to start
 }
 
+// days in a week
+const allWeekDays = 7
+
 // NewScheduler creates a new Scheduler
 func NewScheduler(loc *time.Location) *Scheduler {
 	executor := newExecutor()
@@ -244,8 +247,13 @@ func (s *Scheduler) calculateWeeks(job *Job, lastRun time.Time) time.Duration {
 }
 
 func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekday int, job *Job) int {
-	if job.interval > 1 { // every N weeks counts rest of this week and full N-1 weeks
-		return daysToWeekday + int(job.interval-1)*7
+	if job.interval > 1 && job.RunCount() < len(job.Weekdays()) { // just count weeks after the first jobs were done
+		return daysToWeekday
+	} else if job.interval > 1 && job.RunCount() >= len(job.Weekdays()) {
+		if daysToWeekday > 0 {
+			return int(job.interval)*7 - (allWeekDays - daysToWeekday)
+		}
+		return int(job.interval) * 7
 	}
 
 	if daysToWeekday == 0 { // today, at future time or already passed
@@ -255,7 +263,6 @@ func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekda
 		}
 		return 7
 	}
-
 	return daysToWeekday
 }
 
@@ -327,27 +334,29 @@ func shouldRunAtSpecificTime(job *Job) bool {
 
 func remainingDaysToWeekday(from time.Weekday, weekDays []time.Weekday) int {
 	var (
-		to                    time.Weekday
-		daysUntilScheduledDay int
+		daysUntilScheduledDay         int
+		daysUntilScheduledDayPositive = allWeekDays
+		daysUntilScheduledDayNegative = 0
 	)
-	if len(weekDays) == 1 {
-		to = weekDays[0]
 
-		daysUntilScheduledDay = int(to) - int(from)
-		if daysUntilScheduledDay < 0 {
-			daysUntilScheduledDay += 7
+	for _, day := range weekDays {
+		differenceBetweenDays := int(day) - int(from)
+		// checking only if is smaller than max cause there is no way to be equals
+		if differenceBetweenDays > 0 && differenceBetweenDays < daysUntilScheduledDayPositive {
+			daysUntilScheduledDayPositive = differenceBetweenDays
 		}
-	} else {
-		daysUntilScheduledDay = 8
-		for _, day := range weekDays {
-			differenceBetweenDays := int(day) - int(from)
 
-			// checking only if is smaller than max cause there is no way to be equals
-			if differenceBetweenDays > 0 && differenceBetweenDays < daysUntilScheduledDay {
-				daysUntilScheduledDay = differenceBetweenDays
-			}
+		// mapping negative days to repeat jobs
+		if differenceBetweenDays < 0 && differenceBetweenDays < daysUntilScheduledDayNegative {
+			daysUntilScheduledDayNegative = differenceBetweenDays
 		}
 	}
+
+	if daysUntilScheduledDayPositive > 0 && daysUntilScheduledDayPositive != allWeekDays {
+		daysUntilScheduledDay = daysUntilScheduledDayPositive
+	} else if daysUntilScheduledDayNegative < 0 {
+		daysUntilScheduledDay = allWeekDays + daysUntilScheduledDayNegative
+	}
 	return daysUntilScheduledDay
 }
 


Test patch:
diff --git a/scheduler_test.go b/scheduler_test.go
index 5e02d59..6ba8c8e 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -677,7 +677,7 @@ func TestScheduler_CalculateNextRun(t *testing.T) {
 		//// WEEKDAYS
 		{name: "every weekday starting on one day before it should run this weekday", job: &Job{interval: 1, unit: weeks, scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
 		{name: "every weekday starting on same weekday should run on same immediately", job: &Job{interval: 1, unit: weeks, scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 0},
-		{name: "every 2 weekdays counting this week's weekday should run next weekday", job: &Job{interval: 2, unit: weeks, scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0)}, wantTimeUntilNextRun: 8 * day},
+		{name: "every 2 weekdays counting this week's weekday should run next weekday", job: &Job{interval: 2, unit: weeks, scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0)}, wantTimeUntilNextRun: day},
 		{name: "every weekday starting on one day after should count days remaining", job: &Job{interval: 1, unit: weeks, scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 2)}, wantTimeUntilNextRun: 6 * day},
 		{name: "every weekday starting before jobs .At() time should run at same day at time", job: &Job{interval: 1, unit: weeks, atTime: _getHours(9) + _getMinutes(30), scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: _getHours(9) + _getMinutes(30)},
 		{name: "every weekday starting at same day at time that already passed should run at next week at time", job: &Job{interval: 1, unit: weeks, atTime: _getHours(9) + _getMinutes(30), scheduledWeekday: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(10, 30, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 6*day + _getHours(23) + _getMinutes(0)},
@@ -1430,3 +1430,66 @@ func TestScheduler_CheckNextWeekDay(t *testing.T) {
 
 	})
 }
+
+func TestScheduler_CheckEveryWeekHigherThanOne(t *testing.T) {
+	januaryDay2020At := func(day int) time.Time {
+		return time.Date(2020, time.January, day, 0, 0, 0, 0, time.UTC)
+	}
+
+	testCases := []struct {
+		description string
+		interval    int
+		weekDays    []time.Weekday
+		daysToTest  []int
+		caseTest    int
+	}{
+		{description: "every two weeks after run the first scheduled task", interval: 2, weekDays: []time.Weekday{time.Thursday}, daysToTest: []int{1, 2}, caseTest: 1},
+		{description: "every three weeks after run the first scheduled task", interval: 3, weekDays: []time.Weekday{time.Thursday}, daysToTest: []int{1, 2}, caseTest: 2},
+		{description: "every two weeks after run the first 2 scheduled tasks", interval: 2, weekDays: []time.Weekday{time.Thursday, time.Friday}, daysToTest: []int{1, 2, 3}, caseTest: 3},
+	}
+
+	const (
+		wantTimeUntilNextRunOneDay = 24 * time.Hour
+		// two weeks difference
+		wantTimeUntilNextRunTwoWeeks = 24 * time.Hour * 14
+		// three weeks difference
+		wantTimeUntilNextRunThreeWeeks = 24 * time.Hour * 21
+		// two weeks difference less one day
+		wantTimeUntilNextRunTwoWeeksLessOneDay = 24 * time.Hour * (14 - 1)
+	)
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			s := NewScheduler(time.UTC)
+			s.Every(tc.interval)
+
+			for _, weekDay := range tc.weekDays {
+				s.Weekday(weekDay)
+			}
+			job, err := s.Do(func() {})
+			require.NoError(t, err)
+			for numJob, day := range tc.daysToTest {
+				lastRun := januaryDay2020At(day)
+
+				job.lastRun = lastRun
+				got := s.durationToNextRun(lastRun, job)
+
+				if numJob < len(tc.weekDays) {
+					assert.Equal(t, wantTimeUntilNextRunOneDay, got)
+				} else {
+					if tc.caseTest == 1 {
+						assert.Equal(t, wantTimeUntilNextRunTwoWeeks, got)
+					} else if tc.caseTest == 2 {
+						assert.Equal(t, wantTimeUntilNextRunThreeWeeks, got)
+					} else if tc.caseTest == 3 {
+						assert.Equal(t, wantTimeUntilNextRunTwoWeeksLessOneDay, got)
+					}
+
+				}
+				job.runCount++
+			}
+
+		})
+	}
+
+}