Repository: go-co-op/gocron
Pull request #310 (2022-03-07 22:49:44)

Add every first weekday of month (#274)
### What does this do?
It allows us to set the job to every first weekday of month

### Which issue(s) does this PR fix/relate to?
#274 

### Have you included tests for your changes?
Of course.

### Did you document any new/modified functionality?
Updated `example_test.go`

AlexanderSutul: @JohnRoesler  Could you take a look, please. 
Sorry for the very long waiting again 😊 
AlexanderSutul: @JohnRoesler  Hey! Your suggestions about renaming have been added. Could you take a look again?

Patch:
diff --git a/scheduler.go b/scheduler.go
index 7d3bb72..08c0ef4 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -696,6 +696,17 @@ Jobs:
 	return nil, ErrJobNotFoundWithTag
 }
 
+// MonthFirstWeekday sets the job to run the first specified weekday of the month
+func (s *Scheduler) MonthFirstWeekday(weekday time.Weekday) *Scheduler {
+	_, month, day := s.time.Now(time.UTC).Date()
+
+	if day < 7 {
+		return s.Cron(fmt.Sprintf("0 0 %d %d %d", day, month, weekday))
+	}
+
+	return s.Cron(fmt.Sprintf("0 0 %d %d %d", day, month+1, weekday))
+}
+
 // LimitRunsTo limits the number of executions of this job to n.
 // Upon reaching the limit, the job is removed from the scheduler.
 func (s *Scheduler) LimitRunsTo(i int) *Scheduler {


Test patch:
diff --git a/example_test.go b/example_test.go
index 9378de1..69b761d 100644
--- a/example_test.go
+++ b/example_test.go
@@ -430,6 +430,12 @@ func ExampleScheduler_Month() {
 	_, _ = s.Month(1, 2).Every(1).Do(task)
 }
 
+func ExampleScheduler_MonthFirstWeekday() {
+	s := gocron.NewScheduler(time.UTC)
+	_, _ = s.MonthFirstWeekday(time.Monday).Do(task)
+	s.StartAsync()
+}
+
 func ExampleScheduler_MonthLastDay() {
 	s := gocron.NewScheduler(time.UTC)
 
diff --git a/scheduler_test.go b/scheduler_test.go
index 18c7977..820ff70 100644
--- a/scheduler_test.go
+++ b/scheduler_test.go
@@ -1875,6 +1875,50 @@ func TestScheduler_Midday(t *testing.T) {
 	})
 }
 
+func TestScheduler_EveryMonthFirstWeekday(t *testing.T) {
+	testCases := []struct {
+		current  time.Time
+		expected time.Time
+		weekday  time.Weekday
+	}{
+		{
+			current:  time.Date(2022, time.March, 7, 0, 0, 0, 0, time.UTC),
+			expected: time.Date(2022, time.April, 6, 0, 0, 0, 0, time.UTC),
+			weekday:  time.Wednesday,
+		},
+		{
+			current:  time.Date(2022, time.March, 1, 0, 0, 0, 0, time.UTC),
+			expected: time.Date(2022, time.March, 3, 0, 0, 0, 0, time.UTC),
+			weekday:  time.Thursday,
+		},
+		{
+			current:  time.Date(2022, time.March, 23, 0, 0, 0, 0, time.UTC),
+			expected: time.Date(2022, time.April, 4, 0, 0, 0, 0, time.UTC),
+			weekday:  time.Monday,
+		},
+		{
+			current:  time.Date(2022, time.February, 28, 0, 0, 0, 0, time.UTC),
+			expected: time.Date(2022, time.March, 7, 0, 0, 0, 0, time.UTC),
+			weekday:  time.Monday,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run("check first weekday of the month", func(t *testing.T) {
+			s := NewScheduler(time.UTC)
+			s.time = fakeTime{
+				onNow: func(l *time.Location) time.Time {
+					return tc.current
+				},
+			}
+			job, _ := s.MonthFirstWeekday(tc.weekday).Do(func() {})
+			job.lastRun = tc.current
+			durationToNextTime := s.durationToNextRun(tc.current, job)
+			assert.Equal(t, tc.expected, durationToNextTime.dateTime)
+		})
+	}
+}
+
 func TestScheduler_CheckNextWeekDay(t *testing.T) {
 	januaryFirst2020At := func(hour, minute, second int) time.Time {
 		return time.Date(2020, time.January, 1, hour, minute, second, 0, time.UTC)