func _tuesdayWeekday() *time.Weekday {
	tuesday := time.Tuesday
	return &tuesday
}
helper test method
package gocron
import (
	"fmt"
	"log"
	"sync"
	"sync/atomic"
	"testing"
	"time"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)
var _ timeWrapper = (*fakeTime)(nil)
type fakeTime struct {
	onNow func(location *time.Location) time.Time
}
func (f fakeTime) Now(loc *time.Location) time.Time {
	return f.onNow(loc)
}
func (f fakeTime) Unix(i int64, i2 int64) time.Time {
	panic("implement me")
}
func (f fakeTime) Sleep(duration time.Duration) {
	panic("implement me")
}
func task() {
	fmt.Println("I am a running job.")
}
func taskWithParams(a int, b string) {
	fmt.Println(a, b)
}
func TestImmediateExecution(t *testing.T) {
	s := NewScheduler(time.UTC)
	semaphore := make(chan bool)
	_, err := s.Every(1).Second().Do(func() {
		semaphore <- true
	})
	require.NoError(t, err)
	s.StartAsync()
	select {
	case <-time.After(1 * time.Second):
		t.Fatal("job did not run immediately")
	case <-semaphore:
	}
}
func TestScheduler_Every_InvalidInterval(t *testing.T) {
	testCases := []struct {
		description   string
		interval      interface{}
		expectedError string
	}{
		{"zero", 0, ErrInvalidInterval.Error()},
		{"negative", -1, ErrInvalidInterval.Error()},
		{"invalid string duration", "bad", "time: invalid duration \"bad\""},
	}
	s := NewScheduler(time.UTC)
	for _, tc := range testCases {
		t.Run(tc.description, func(t *testing.T) {
			_, err := s.Every(tc.interval).Do(func() {})
			require.Error(t, err)
			assert.EqualError(t, err, tc.expectedError)
		})
	}
}
func TestScheduler_Every(t *testing.T) {
	t.Run("time.Duration", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		semaphore := make(chan bool)
		_, err := s.Every(100 * time.Millisecond).Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		var counter int
		now := time.Now()
		for time.Now().Before(now.Add(500 * time.Millisecond)) {
			if <-semaphore {
				counter++
			}
		}
		s.Stop()
		assert.Equal(t, 6, counter)
	})
	t.Run("int", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		semaphore := make(chan bool)
		_, err := s.Every(100).Milliseconds().Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		var counter int
		now := time.Now()
		for time.Now().Before(now.Add(100 * time.Millisecond)) {
			if <-semaphore {
				counter++
			}
		}
		s.Stop()
		assert.Equal(t, 2, counter)
	})
	t.Run("string duration", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		semaphore := make(chan bool)
		_, err := s.Every("100ms").Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		var counter int
		now := time.Now()
		for time.Now().Before(now.Add(100 * time.Millisecond)) {
			if <-semaphore {
				counter++
			}
		}
		s.Stop()
		assert.Equal(t, 2, counter)
	})
}
func TestExecutionSeconds(t *testing.T) {
	s := NewScheduler(time.UTC)
	jobDone := make(chan bool)
	var (
		executions         []int64
		interval           = 1
		expectedExecutions = 2
		mu                 sync.RWMutex
	)
	runTime := 1 * time.Second
	startTime := time.Now()
	_, err := s.Every(interval).Do(func() {
		mu.Lock()
		defer mu.Unlock()
		executions = append(executions, time.Now().UTC().Unix())
		if time.Now().After(startTime.Add(runTime)) {
			jobDone <- true
		}
	})
	require.NoError(t, err)
	s.StartAsync()
	<-jobDone  
	s.Stop()
	mu.RLock()
	defer mu.RUnlock()
	assert.Equal(t, expectedExecutions, len(executions), "did not run expected number of times")
	for i := 1; i < expectedExecutions; i++ {
		durationBetweenExecutions := executions[i] - executions[i-1]
		assert.Equal(t, int64(interval), durationBetweenExecutions, "duration between tasks does not correspond to expectations")
	}
}
func TestScheduled(t *testing.T) {
	t.Run("simple", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		_, err := s.Every(1).Second().Do(task)
		require.NoError(t, err)
		assert.True(t, s.TaskPresent(task))
	})
	t.Run("with tag", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		_, err := s.Every(1).Hour().Tag("my_custom_tag").Do(task)
		require.NoError(t, err)
		assert.True(t, s.TaskPresent(task))
	})
}
func TestAt(t *testing.T) {
	t.Run("job scheduled for future hasn't run yet", func(t *testing.T) {
		ft := fakeTime{onNow: func(l *time.Location) time.Time {
			return time.Date(1970, 1, 1, 12, 0, 0, 0, l)
		}}
		s := NewScheduler(time.UTC)
		s.time = ft
		now := ft.onNow(time.UTC)
		semaphore := make(chan bool)
		nextMinuteTime := now.Add(1 * time.Minute)
		startAt := fmt.Sprintf("%02d:%02d:%02d", nextMinuteTime.Hour(), nextMinuteTime.Minute(), nextMinuteTime.Second())
		dayJob, err := s.Every(1).Day().At(startAt).Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		time.Sleep(1 * time.Second)
		select {
		case <-time.After(1 * time.Second):
			log.Println(now.Add(time.Minute))
			log.Println(dayJob.nextRun)
			assert.Equal(t, now.Add(1*time.Minute), dayJob.nextRun)
		case <-semaphore:
			t.Fatal("job ran even though scheduled in future")
		}
	})
	t.Run("error due to bad time format", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		badTime := "0:0"
		_, err := s.Every(1).Day().At(badTime).Do(func() {})
		assert.EqualError(t, err, ErrUnsupportedTimeFormat.Error())
		assert.Zero(t, s.Len())
	})
}
func schedulerForNextOrPreviousWeekdayEveryNTimes(weekday time.Weekday, next bool, n int, s *Scheduler) *Scheduler {
	switch weekday {
	case time.Monday:
		if next {
			s = s.Every(n).Tuesday()
		} else {
			s = s.Every(n).Sunday()
		}
	case time.Tuesday:
		if next {
			s = s.Every(n).Wednesday()
		} else {
			s = s.Every(n).Monday()
		}
	case time.Wednesday:
		if next {
			s = s.Every(n).Thursday()
		} else {
			s = s.Every(n).Tuesday()
		}
	case time.Thursday:
		if next {
			s = s.Every(n).Friday()
		} else {
			s = s.Every(n).Wednesday()
		}
	case time.Friday:
		if next {
			s = s.Every(n).Saturday()
		} else {
			s = s.Every(n).Thursday()
		}
	case time.Saturday:
		if next {
			s = s.Every(n).Sunday()
		} else {
			s = s.Every(n).Friday()
		}
	case time.Sunday:
		if next {
			s = s.Every(n).Monday()
		} else {
			s = s.Every(n).Saturday()
		}
	}
	return s
}
func TestWeekdayBeforeToday(t *testing.T) {
	now := time.Now().In(time.UTC)
	s := NewScheduler(time.UTC)
	s = schedulerForNextOrPreviousWeekdayEveryNTimes(now.Weekday(), false, 1, s)
	weekJob, err := s.Do(task)
	require.NoError(t, err)
	s.scheduleNextRun(weekJob)
	sixDaysFromNow := now.AddDate(0, 0, 6)
	exp := time.Date(sixDaysFromNow.Year(), sixDaysFromNow.Month(), sixDaysFromNow.Day(), 0, 0, 0, 0, time.UTC)
	assert.Equal(t, exp, weekJob.nextRun)
}
func TestWeekdayAt(t *testing.T) {
	t.Run("asserts weekday scheduling starts at the current week", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		now := time.Now().UTC()
		s = schedulerForNextOrPreviousWeekdayEveryNTimes(now.Weekday(), true, 1, s)
		weekdayJob, _ := s.Do(task)
		s.scheduleNextRun(weekdayJob)
		tomorrow := now.AddDate(0, 0, 1)
		exp := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, time.UTC)
		nextRun := weekdayJob.nextRun
		nextRunDate := time.Date(nextRun.Year(), nextRun.Month(), nextRun.Day(), 0, 0, 0, 0, time.UTC)
		assert.Equal(t, exp, nextRunDate)
	})
}
func TestScheduler_Remove(t *testing.T) {
	t.Run("remove from non-running", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.TagsUnique()
		_, err := s.Every(1).Minute().Tag("tag1").Do(task)
		require.NoError(t, err)
		_, err = s.Every(1).Minute().Do(taskWithParams, 1, "hello")
		require.NoError(t, err)
		_, err = s.Every(1).Minute().Do(task)
		require.NoError(t, err)
		assert.Equal(t, 3, s.Len(), "Incorrect number of jobs")
		s.Remove(task)
		assert.Equal(t, 1, s.Len(), "Incorrect number of jobs after removing 2 job")
		s.Remove(task)
		assert.Equal(t, 1, s.Len(), "Incorrect number of jobs after removing non-existent job")
	})
	t.Run("remove from running scheduler", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		semaphore := make(chan bool)
		j, err := s.Every("100ms").StartAt(s.time.Now(s.location).Add(time.Second)).Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		s.Remove(j.function)
		select {
		case <-time.After(200 * time.Millisecond):
		case <-semaphore:
			t.Fatal("job ran after being removed")
		}
	})
}
func TestScheduler_RemoveByReference(t *testing.T) {
	t.Run("remove from non-running scheduler", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		job1, _ := s.Every(1).Minute().Do(task)
		job2, _ := s.Every(1).Minute().Do(taskWithParams, 1, "hello")
		assert.Equal(t, 2, s.Len(), "Incorrect number of jobs")
		s.RemoveByReference(job1)
		assert.ElementsMatch(t, []*Job{job2}, s.Jobs())
	})
	t.Run("remove from running scheduler", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.TagsUnique()
		semaphore := make(chan bool)
		j, err := s.Every("100ms").StartAt(s.time.Now(s.location).Add(100 * time.Millisecond)).Tag("tag1").Do(func() {
			semaphore <- true
		})
		require.NoError(t, err)
		s.StartAsync()
		s.RemoveByReference(j)
		select {
		case <-time.After(200 * time.Millisecond):
		case <-semaphore:
			t.Fatal("job ran after being removed")
		}
		_, ok := s.tags.Load("tag1")
		assert.False(t, ok)
	})
}
func TestScheduler_RemoveByTag(t *testing.T) {
	t.Run("non unique tags", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		tag1 := "a"
		tag2 := "ab"
		_, err := s.Every(1).Second().Tag(tag1).Do(taskWithParams, 1, "hello")  
		require.NoError(t, err)
		_, err = s.Every(1).Second().Tag(tag2).Do(taskWithParams, 2, "world")  
		require.NoError(t, err)
		assert.Equal(t, s.Jobs()[0].Tags()[0], tag1, "Job With Tag 'a' is removed from index 0")
		err = s.RemoveByTag(tag1)
		require.NoError(t, err)
		assert.Equal(t, 1, s.Len(), "Incorrect number of jobs after removing 1 job")
		assert.Equal(t, s.Jobs()[0].Tags()[0], tag2, "Job With Tag 'tag two' is removed from index 0")
		err = s.RemoveByTag(tag1)
		assert.EqualError(t, err, ErrJobNotFoundWithTag.Error())
	})
	t.Run("unique tags", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.TagsUnique()
		tag1 := "tag one"
		tag2 := "tag two"
		_, err := s.Every(1).Second().Tag(tag1).Do(taskWithParams, 1, "hello")  
		require.NoError(t, err)
		_, err = s.Every(1).Second().Tag(tag2).Do(taskWithParams, 2, "world")  
		require.NoError(t, err)
		err = s.RemoveByTag("tag one")
		require.NoError(t, err)
		_, err = s.Every(1).Second().Tag(tag1).Do(taskWithParams, 1, "hello")
		assert.Nil(t, err, "Unique tag is not deleted when removing by tag")
	})
}
func TestScheduler_Jobs(t *testing.T) {
	s := NewScheduler(time.UTC)
	s.Every(1).Minute().Do(task)
	s.Every(2).Minutes().Do(task)
	s.Every(3).Minutes().Do(task)
	s.Every(4).Minutes().Do(task)
	js := s.Jobs()
	assert.Len(t, js, 4)
}
func TestScheduler_Len(t *testing.T) {
	s := NewScheduler(time.UTC)
	s.Every(1).Minute().Do(task)
	s.Every(2).Minutes().Do(task)
	s.Every(3).Minutes().Do(task)
	s.Every(4).Minutes().Do(task)
	l := s.Len()
	assert.Equal(t, l, 4)
}
func TestSwap(t *testing.T) {
	s := NewScheduler(time.UTC)
	_, err := s.Every(1).Minute().Do(task)
	require.NoError(t, err)
	_, err = s.Every(2).Minute().Do(task)
	require.NoError(t, err)
	jb := s.Jobs()
	var jobsBefore []*Job
	jobsBefore = append(jobsBefore, jb...)
	s.Swap(1, 0)
	jobsAfter := s.Jobs()
	assert.Equal(t, jobsBefore[0], jobsAfter[1])
	assert.Equal(t, jobsBefore[1], jobsAfter[0])
}
func TestLess(t *testing.T) {
	s := NewScheduler(time.UTC)
	s.Every(1).Minute().Do(task)
	s.Every(2).Minute().Do(task)
	assert.True(t, s.Less(0, 1))
}
func TestSetLocation(t *testing.T) {
	s := NewScheduler(time.FixedZone("UTC-8", -8*60*60))
	assert.Equal(t, time.FixedZone("UTC-8", -8*60*60), s.Location())
	s.ChangeLocation(time.UTC)
	assert.Equal(t, time.UTC, s.Location())
}
func TestClear(t *testing.T) {
	s := NewScheduler(time.UTC)
	semaphore := make(chan bool)
	_, err := s.Every("100ms").Do(func() {
		semaphore <- true
	})
	require.NoError(t, err)
	s.StartAsync()
	s.Clear()
	assert.Equal(t, 0, s.Len())
	var counter int
	now := time.Now()
	for time.Now().Before(now.Add(200 * time.Millisecond)) {
		select {
		case <-semaphore:
			counter++
		default:
		}
	}
	assert.Equal(t, 1, counter)
}
func TestClearUnique(t *testing.T) {
	s := NewScheduler(time.UTC)
	s.TagsUnique()
	semaphore := make(chan bool)
	_, err := s.Every("100ms").Tag("tag1").Do(func() {
		semaphore <- true
	})
	require.NoError(t, err)
	s.StartAsync()
	s.Clear()
	assert.Equal(t, 0, s.Len())
	var counter int
	now := time.Now()
	for time.Now().Before(now.Add(200 * time.Millisecond)) {
		select {
		case <-semaphore:
			counter++
		default:
		}
	}
	assert.Equal(t, 1, counter)
	s.tags.Range(func(key, value interface{}) bool {
		assert.FailNow(t, "map should be empty")
		return true
	})
}
func TestSetUnit(t *testing.T) {
	testCases := []struct {
		desc     string
		timeUnit schedulingUnit
	}{
		{"seconds", seconds},
		{"minutes", minutes},
		{"hours", hours},
		{"days", days},
		{"weeks", weeks},
	}
	for _, tc := range testCases {
		s := NewScheduler(time.UTC)
		t.Run(tc.desc, func(t *testing.T) {
			switch tc.timeUnit {
			case seconds:
				s.Every(2).Seconds().Do(task)
			case minutes:
				s.Every(2).Minutes().Do(task)
			case hours:
				s.Every(2).Hours().Do(task)
			case days:
				s.Every(2).Days().Do(task)
			case weeks:
				s.Every(2).Weeks().Do(task)
			}
			j := s.jobs[0]
			assert.Equal(t, tc.timeUnit, j.unit)
		})
	}
}
func TestScheduler_Stop(t *testing.T) {
	t.Run("stops a running scheduler", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.StartAsync()
		assert.True(t, s.IsRunning())
		s.Stop()
		assert.False(t, s.IsRunning())
	})
	t.Run("stops a running scheduler calling .Stop()", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.StartAsync()
		assert.True(t, s.IsRunning())
		s.Stop()
		time.Sleep(1 * time.Millisecond)  
		assert.False(t, s.IsRunning())
	})
	t.Run("noop on stopped scheduler", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		s.Stop()
		assert.False(t, s.IsRunning())
	})
	t.Run("waits for jobs to finish processing before returning .Stop()", func(t *testing.T) {
		t.Parallel()
		i := int32(0)
		s := NewScheduler(time.UTC)
		s.Every(10).Second().Do(func() {
			time.Sleep(2 * time.Second)
			atomic.AddInt32(&i, 1)
		})
		s.StartAsync()
		time.Sleep(1 * time.Second)  
		s.Stop()
		assert.EqualValues(t, 1, atomic.LoadInt32(&i))
	})
}
func TestScheduler_StartAt(t *testing.T) {
	t.Run("scheduling", func(t *testing.T) {
		s := NewScheduler(time.Local)
		now := time.Now()
		job, _ := s.Every(3).Seconds().StartAt(now.Add(time.Second * 5)).Do(func() {})
		assert.False(t, job.getStartsImmediately())
		s.start()
		assert.Equal(t, now.Add(time.Second*5).Truncate(time.Second), job.NextRun().Truncate(time.Second))
		s.stop()
		job, _ = s.Every(3).Seconds().Do(func() {})
		assert.True(t, job.getStartsImmediately())
	})
	t.Run("run", func(t *testing.T) {
		s := NewScheduler(time.UTC)
		semaphore := make(chan bool)
		s.Every(1).Day().StartAt(s.time.Now(s.location).Add(100 * time.Millisecond)).Do(func() {
			semaphore <- true
		})
		s.StartAsync()
		select {
		case <-time.After(200 * time.Millisecond):
			t.Fatal("job did not run at 1 second")
		case <-semaphore:
		}
	})
	t.Run("start in past", func(t *testing.T) {
		s := NewScheduler(time.Local)
		now := time.Now()
		job, _ := s.Every(24).Hours().StartAt(now.Add(-24 * time.Hour).Add(10 * time.Minute)).Do(func() {})
		assert.False(t, job.getStartsImmediately())
		s.start()
		assert.Equal(t, now.Add(10*time.Minute).Truncate(time.Second), job.NextRun().Truncate(time.Second))
		s.stop()
	})
}
func TestScheduler_CalculateNextRun(t *testing.T) {
	ft := fakeTime{onNow: func(l *time.Location) time.Time {
		return time.Date(1970, 1, 1, 12, 0, 0, 0, l)
	}}
	day := time.Hour * 24
	januaryFirst2020At := func(hour, minute, second int) time.Time {
		return time.Date(2020, time.January, 1, hour, minute, second, 0, time.UTC)
	}
	mondayAt := func(hour, minute, second int) time.Time {
		return time.Date(2020, time.January, 6, hour, minute, second, 0, time.UTC)
	}
	testCases := []struct {
		name                 string
		job                  *Job
		wantTimeUntilNextRun time.Duration
	}{
		{name: "every second test", job: &Job{interval: 1, unit: seconds, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getSeconds(1)},
		{name: "every 62 seconds test", job: &Job{interval: 62, unit: seconds, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getSeconds(62)},
		{name: "every minute test", job: &Job{interval: 1, unit: minutes, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getMinutes(1)},
		{name: "every 62 minutes test", job: &Job{interval: 62, unit: minutes, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getMinutes(62)},
		{name: "every hour test", job: &Job{interval: 1, unit: hours, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(1)},
		{name: "every 25 hours test", job: &Job{interval: 25, unit: hours, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(25)},
		{name: "every day at midnight", job: &Job{interval: 1, unit: days, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
		{name: "every day at 09:30AM with scheduler starting before 09:30AM should run at same day at time", job: &Job{interval: 1, unit: days, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: _getHours(9) + _getMinutes(30)},
		{name: "every day at 09:30AM which just ran should run tomorrow at 09:30AM", job: &Job{interval: 1, unit: days, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 1 * day},
		{name: "every 31 days at midnight should run 31 days later", job: &Job{interval: 31, unit: days, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * day},
		{name: "daily job just ran at 8:30AM and should be scheduled for next day's 8:30AM", job: &Job{interval: 1, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(8, 30, 0)}, wantTimeUntilNextRun: 24 * time.Hour},
		{name: "daily job just ran at 5:30AM and should be scheduled for today at 8:30AM", job: &Job{interval: 1, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: 3 * time.Hour},
		{name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: (2 * day) + 3*time.Hour},
		{name: "job runs every 2 days, just ran at 8:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(8, 30, 0)}, wantTimeUntilNextRun: 2 * day},
		{name: "daily, last run was 1 second ago", job: &Job{interval: 1, unit: days, atTime: 12 * time.Hour, lastRun: ft.Now(time.UTC).Add(-time.Second)}, wantTimeUntilNextRun: 1 * day},
		{name: "every week should run in 7 days", job: &Job{interval: 1, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 7 * day},
		{name: "every week with .At time rule should run respect .At time rule", job: &Job{interval: 1, atTime: _getHours(9) + _getMinutes(30), unit: weeks, lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 7 * day},
		{name: "every two weeks at 09:30AM should run in 14 days at 09:30AM", job: &Job{interval: 2, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 14 * day},
		{name: "every 31 weeks ran at jan 1st at midnight should run at August 5, 2020", job: &Job{interval: 31, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * 7 * day},
		{name: "every month in a 31 days month should be scheduled for 31 days ahead", job: &Job{interval: 1, unit: months, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31 * day},
		{name: "every month in a 30 days month should be scheduled for 30 days ahead", job: &Job{interval: 1, unit: months, lastRun: time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 30 * day},
		{name: "every month at february on leap year should count 29 days", job: &Job{interval: 1, unit: months, lastRun: time.Date(2020, time.February, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 29 * day},
		{name: "every month at february on non leap year should count 28 days", job: &Job{interval: 1, unit: months, lastRun: time.Date(2019, time.February, 1, 0, 0, 0, 0, time.UTC)}, wantTimeUntilNextRun: 28 * day},
		{name: "every month at first day at time should run next month + at time", job: &Job{interval: 1, unit: months, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30)},
		{name: "every month at day should consider at days", job: &Job{interval: 1, unit: months, daysOfTheMonth: []int{2}, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 1 * day},
		{name: "every month at day should consider at hours", job: &Job{interval: 1, unit: months, atTime: _getHours(9) + _getMinutes(30), lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + _getHours(9) + _getMinutes(30)},
		{name: "every month on the first day, but started on january 8th, should run February 1st", job: &Job{interval: 1, unit: months, daysOfTheMonth: []int{1}, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 7)}, wantTimeUntilNextRun: 24 * day},
		{name: "every 2 months at day 1, starting at day 1, should run in 2 months", job: &Job{interval: 2, unit: months, daysOfTheMonth: []int{1}, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + 29*day},                           
		{name: "every 2 months at day 2, starting at day 1, should run in 2 months + 1 day", job: &Job{interval: 2, unit: months, daysOfTheMonth: []int{2}, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 31*day + 29*day + 1*day},           
		{name: "every 2 months at day 1, starting at day 2, should run in 2 months - 1 day", job: &Job{interval: 2, unit: months, daysOfTheMonth: []int{1}, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 30*day + 29*day},  
		{name: "every 13 months at day 1, starting at day 2 run in 13 months - 1 day", job: &Job{interval: 13, unit: months, daysOfTheMonth: []int{1}, lastRun: januaryFirst2020At(0, 0, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: januaryFirst2020At(0, 0, 0).AddDate(0, 13, -1).Sub(januaryFirst2020At(0, 0, 0))},
		{name: "every weekday starting on one day before it should run this weekday", job: &Job{interval: 1, unit: weeks, scheduledWeekdays: []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, scheduledWeekdays: []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, scheduledWeekdays: []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, scheduledWeekdays: []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), scheduledWeekdays: []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), scheduledWeekdays: []time.Weekday{*_tuesdayWeekday()}, lastRun: mondayAt(10, 30, 0).AddDate(0, 0, 1)}, wantTimeUntilNextRun: 6*day + _getHours(23) + _getMinutes(0)},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			s := NewScheduler(time.UTC)
			s.time = ft
			got := s.durationToNextRun(tc.job.LastRun(), tc.job).duration
			assert.Equalf(t, tc.wantTimeUntilNextRun, got, fmt.Sprintf("expected %s / got %s", tc.wantTimeUntilNextRun.String(), got.String()))
		})
	}
}