package internal

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestNewTimeOfDay(t *testing.T) {
	to := NewTimeOfDay(12, 34, 4)
	assert.Equal(t, 12, to.hour)
	assert.Equal(t, 34, to.minute)
	assert.Equal(t, 4, to.second)
	assert.Equal(t, 45244*time.Second, to.d)
}

func TestParseTime(t *testing.T) {
	to, err := ParseTimeOfDay("12:34:04")
	assert.Nil(t, err)
	assert.Equal(t, NewTimeOfDay(12, 34, 4), to)

	_, err = ParseTimeOfDay("0:0:0")
	assert.NotNil(t, err)

	_, err = ParseTimeOfDay("00:00")
	assert.NotNil(t, err)

	_, err = ParseTimeOfDay("0000:00")
	assert.NotNil(t, err)
}

func TestNewUTCTimeRange(t *testing.T) {
	r, err := NewUTCTimeRange(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), []time.Weekday{})
	assert.Nil(t, err)
	assert.Equal(t, NewTimeOfDay(3, 0, 0), r.startTime)
	assert.Equal(t, NewTimeOfDay(18, 0, 0), r.endTime)
	assert.Equal(t, []time.Weekday{}, r.weekdays)
	assert.Nil(t, r.startDay)
	assert.Nil(t, r.endDay)
	assert.Equal(t, time.UTC, r.loc)
}

func TestNewTimeRangeInLocation(t *testing.T) {
	r, err := NewTimeRangeInLocation(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), []time.Weekday{}, time.Local)
	assert.Nil(t, err)
	assert.Equal(t, NewTimeOfDay(3, 0, 0), r.startTime)
	assert.Equal(t, NewTimeOfDay(18, 0, 0), r.endTime)
	assert.Equal(t, []time.Weekday{}, r.weekdays)
	assert.Nil(t, r.startDay)
	assert.Nil(t, r.endDay)
	assert.Equal(t, time.Local, r.loc)
}

func TestNewTimeRangeInNilLocation(t *testing.T) {
	_, err := NewTimeRangeInLocation(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), []time.Weekday{}, nil)
	assert.EqualError(t, err, "time: missing Location in call to NewTimeRangeInLocation")
}

func TestNewUTCWeekRange(t *testing.T) {
	r, err := NewUTCWeekRange(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), time.Monday, time.Wednesday)
	assert.Nil(t, err)
	assert.Equal(t, NewTimeOfDay(3, 0, 0), r.startTime)
	assert.Equal(t, NewTimeOfDay(18, 0, 0), r.endTime)
	assert.NotNil(t, r.startDay)
	assert.NotNil(t, r.endDay)
	assert.Equal(t, []time.Weekday{}, r.weekdays)
	assert.Equal(t, time.Monday, *r.startDay)
	assert.Equal(t, time.Wednesday, *r.endDay)
	assert.Equal(t, time.UTC, r.loc)
}

func TestNewWeekRangeInLocation(t *testing.T) {
	r, err := NewWeekRangeInLocation(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), time.Monday, time.Wednesday, time.Local)
	assert.Nil(t, err)
	assert.Equal(t, NewTimeOfDay(3, 0, 0), r.startTime)
	assert.Equal(t, NewTimeOfDay(18, 0, 0), r.endTime)
	assert.NotNil(t, r.startDay)
	assert.NotNil(t, r.endDay)
	assert.Equal(t, []time.Weekday{}, r.weekdays)
	assert.Equal(t, time.Monday, *r.startDay)
	assert.Equal(t, time.Wednesday, *r.endDay)
	assert.Equal(t, time.Local, r.loc)
}

func TestNewWeekRangeInNilLocation(t *testing.T) {
	_, err := NewWeekRangeInLocation(NewTimeOfDay(3, 0, 0), NewTimeOfDay(18, 0, 0), time.Monday, time.Wednesday, nil)
	assert.EqualError(t, err, "time: missing Location in call to NewTimeRangeInLocation")
}

func BenchmarkInRange(b *testing.B) {
	start := NewTimeOfDay(3, 0, 0)
	end := NewTimeOfDay(18, 0, 0)
	weekdays := []time.Weekday{}
	tr, _ := NewUTCTimeRange(start, end, weekdays)

	now := time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC)
	for n := 0; n < b.N; n++ {
		tr.IsInRange(now)
	}
}

func TestTimeRangeIsInRange(t *testing.T) {
	testcases := []struct {
		label           string
		start           TimeOfDay
		end             TimeOfDay
		weekdays        []time.Weekday
		location        *time.Location
		now             time.Time
		expectedInRange bool
	}{
		{
			label:           "10AM",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "6PM",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "2AM",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 2, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "7PM",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "6:01PM",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 18, 1, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "6PM, inverted start/end",
			start:           NewTimeOfDay(18, 0, 0),
			end:             NewTimeOfDay(3, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "3AM, inverted start/end",
			start:           NewTimeOfDay(18, 0, 0),
			end:             NewTimeOfDay(3, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 3, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "4AM, inverted start/end",
			start:           NewTimeOfDay(18, 0, 0),
			end:             NewTimeOfDay(3, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 4, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "5PM, inverted start/end",
			start:           NewTimeOfDay(18, 0, 0),
			end:             NewTimeOfDay(3, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 17, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "3AM in fixed zone of -60",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(5, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.FixedZone("myzone", -60),
			now:             time.Date(2016, time.August, 10, 3, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "3:01AM in fixed zone of -60",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(5, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.FixedZone("myzone", -60),
			now:             time.Date(2016, time.August, 10, 3, 1, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "equal start/end",
			start:           NewTimeOfDay(0, 0, 0),
			end:             NewTimeOfDay(0, 0, 0),
			weekdays:        []time.Weekday{},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "10AM, weekdays in range",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{time.Monday, time.Tuesday, time.Wednesday, time.Thursday},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "10AM, weekdays out of range",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{time.Monday, time.Tuesday},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "2AM, weekdays in range",
			start:           NewTimeOfDay(3, 0, 0),
			end:             NewTimeOfDay(18, 0, 0),
			weekdays:        []time.Weekday{time.Monday, time.Tuesday, time.Wednesday},
			location:        time.UTC,
			now:             time.Date(2016, time.August, 10, 2, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "18:59:00 Sunday, crossing midnight",
			start:           NewTimeOfDay(19, 0, 0),
			end:             NewTimeOfDay(6, 0, 0),
			weekdays:        []time.Weekday{time.Sunday},
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 18, 59, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "5:59AM Monday, crossing midnight",
			start:           NewTimeOfDay(19, 0, 0),
			end:             NewTimeOfDay(6, 0, 0),
			weekdays:        []time.Weekday{time.Sunday},
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 5, 59, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "6:01AM Monday, crossing midnight",
			start:           NewTimeOfDay(19, 0, 0),
			end:             NewTimeOfDay(6, 0, 0),
			weekdays:        []time.Weekday{time.Sunday},
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 6, 1, 0, 0, time.UTC),
			expectedInRange: false,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.label, func(t *testing.T) {
			timeRange, err := NewTimeRangeInLocation(tc.start, tc.end, tc.weekdays, tc.location)
			assert.Nil(t, err)
			assert.Equal(t, tc.expectedInRange, timeRange.IsInRange(tc.now))
		})
	}
}

func TestTimeInRangeNil(t *testing.T) {
	now := time.Date(2016, time.August, 10, 18, 0, 0, 0, time.UTC)

	var tr *TimeRange
	assert.True(t, tr.IsInRange(now), "always in range if the time range is nil")
}

func TestTimeRangeIsInRangeWithDay(t *testing.T) {
	testcases := []struct {
		label           string
		startTime       TimeOfDay
		endTime         TimeOfDay
		startDay        time.Weekday
		endDay          time.Weekday
		weekdays        []time.Weekday
		location        *time.Location
		now             time.Time
		expectedInRange bool
	}{
		{
			label:           "2AM Wednesday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 28, 2, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "6PM Tuesday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 27, 18, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "10PM Tuesday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 27, 22, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "3AM Tuesday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "2:59:59 Monday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 26, 2, 59, 59, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "6:01PM Thursday",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Monday,
			endDay:          time.Thursday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 29, 18, 0, 1, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "2AM Saturday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 24, 2, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "2AM Wednesday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 28, 2, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "3AM Thursday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 22, 3, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "6PM Monday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 26, 18, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "2:59AM Thursday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 22, 2, 59, 59, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "6:01PM Monday, inverted days",
			startTime:       NewTimeOfDay(3, 0, 0),
			endTime:         NewTimeOfDay(18, 0, 0),
			startDay:        time.Thursday,
			endDay:          time.Monday,
			location:        time.UTC,
			now:             time.Date(2004, time.July, 26, 18, 0, 1, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "8:59AM Sunday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 8, 59, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "8:59:01AM Sunday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 8, 59, 1, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "9:01AM Sunday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "9AM Sunday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 9, 0, 0, 0, time.UTC),
			expectedInRange: false,
		},
		{
			label:           "8:59AM Monday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 8, 59, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "8:59:01AM Monday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 8, 59, 1, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "9:01AM Monday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 9, 1, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "9AM Monday, whole week",
			startTime:       NewTimeOfDay(9, 1, 0),
			endTime:         NewTimeOfDay(8, 59, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 9, 0, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "8:59AM Sunday, whole week, inverted times",
			startTime:       NewTimeOfDay(8, 59, 0),
			endTime:         NewTimeOfDay(9, 1, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 8, 59, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "9:01AM Sunday, whole week, inverted times",
			startTime:       NewTimeOfDay(8, 59, 0),
			endTime:         NewTimeOfDay(9, 1, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
			expectedInRange: true,
		},
		{
			label:           "8:59AM Monday, whole week, inverted times",
			startTime:       NewTimeOfDay(8, 59, 0),
			endTime:         NewTimeOfDay(9, 1, 0),
			startDay:        time.Sunday,
			endDay:          time.Sunday,
			location:        time.UTC,
			now:             time.Date(2006, time.December, 4, 8, 59, 0, 0, time.UTC),
			expectedInRange: false,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.label, func(t *testing.T) {
			timeRange, err := NewWeekRangeInLocation(tc.startTime, tc.endTime, tc.startDay, tc.endDay, tc.location)
			assert.Nil(t, err)
			assert.Equal(t, tc.expectedInRange, timeRange.IsInRange(tc.now))
		})
	}
}

func TestTimeRangeIsInSameRange(t *testing.T) {
	testcases := []struct {
		label    string
		start    TimeOfDay
		end      TimeOfDay
		location *time.Location
		weekdays []time.Weekday
		examples []struct {
			label    string
			time1    time.Time
			time2    time.Time
			expected bool
		}
	}{
		{
			label:    "start time is less than end time",
			start:    NewTimeOfDay(3, 0, 0),
			end:      NewTimeOfDay(18, 0, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same time",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time2 in same session but greater",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 11, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time2 in same session but less",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 11, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time1 not in same session",
					time1:    time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "time2 not in same session",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 2, 0, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "time1 within time range but next day",
					time1:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "time2 within time range but next day",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "with weekdays, start time is less than end time",
			start:    NewTimeOfDay(3, 0, 0),
			end:      NewTimeOfDay(18, 0, 0),
			location: time.UTC,
			weekdays: []time.Weekday{time.Monday, time.Tuesday, time.Wednesday},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same time within range",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "same time outside weekdays range",
					time1:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "time2 in same session but greater within range",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 11, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time2 in same session but less within range",
					time1:    time.Date(2016, time.August, 10, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 11, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time2 in same session but greater outside range",
					time1:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 11, 0, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "time2 in same session but less outside range",
					time1:    time.Date(2016, time.August, 11, 10, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 11, 0, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "start time is greater than end time",
			start:    NewTimeOfDay(18, 0, 0),
			end:      NewTimeOfDay(3, 0, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same session, same day",
					time1:    time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 20, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "same session, time2 is in next day",
					time1:    time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 2, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "same session, time1 is in next day",
					time1:    time.Date(2016, time.August, 11, 2, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 19, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "time1 is 25 hours greater than time2",
					time1:    time.Date(2016, time.August, 11, 21, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 20, 0, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "start time is equal to end time",
			start:    NewTimeOfDay(6, 0, 0),
			end:      NewTimeOfDay(6, 0, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "times on different days",
					time1:    time.Date(2016, time.January, 13, 19, 10, 0, 0, time.UTC),
					time2:    time.Date(2016, time.January, 14, 19, 6, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "in different time zone",
			start:    NewTimeOfDay(0, 0, 0),
			end:      NewTimeOfDay(2, 0, 0),
			location: time.FixedZone("myzone", -60),
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same times in range",
					time1:    time.Date(2016, time.August, 10, 0, 1, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 0, 1, 0, 0, time.UTC),
					expected: true,
				},
			},
		},
		{
			label:    "in different time zone, inverted start/end",
			start:    NewTimeOfDay(2, 0, 0),
			end:      NewTimeOfDay(0, 0, 0),
			location: time.FixedZone("myzone", -60),
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same times in range",
					time1:    time.Date(2016, time.August, 10, 2, 1, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 10, 2, 1, 0, 0, time.UTC),
					expected: true,
				},
			},
		},
		{
			label:    "midnight to midnight session in time zone",
			start:    NewTimeOfDay(0, 0, 0),
			end:      NewTimeOfDay(0, 0, 0),
			location: time.FixedZone("custom", -60),
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "different days UTC but same in fixed zone",
					time1:    time.Date(2016, time.August, 10, 0, 0, 0, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 0, 0, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "midnight to midnight session in UTC",
			start:    NewTimeOfDay(0, 0, 0),
			end:      NewTimeOfDay(0, 0, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "spanning midnight",
					time1:    time.Date(2016, time.August, 10, 23, 59, 59, 0, time.UTC),
					time2:    time.Date(2016, time.August, 11, 0, 0, 0, 0, time.UTC),
					expected: false,
				},
			},
		},
		{
			label:    "1:49 to 1:49 session",
			start:    NewTimeOfDay(1, 49, 0),
			end:      NewTimeOfDay(1, 49, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "edgecase 1",
					time1:    time.Date(2016, time.August, 16, 1, 48, 21, 0, time.UTC),
					time2:    time.Date(2016, time.August, 16, 1, 49, 02, 0, time.UTC),
					expected: false,
				},
				{
					label:    "edgecase 2",
					time1:    time.Date(2016, time.August, 16, 13, 48, 21, 0, time.UTC),
					time2:    time.Date(2016, time.August, 16, 13, 49, 02, 0, time.UTC),
					expected: true,
				},
			},
		},
		{
			label:    "13:49 to 13:49 session",
			start:    NewTimeOfDay(13, 49, 0),
			end:      NewTimeOfDay(13, 49, 0),
			location: time.UTC,
			weekdays: []time.Weekday{},
			examples: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "edgecase 1",
					time1:    time.Date(2016, time.August, 16, 13, 48, 21, 0, time.UTC),
					time2:    time.Date(2016, time.August, 16, 13, 49, 02, 0, time.UTC),
					expected: false,
				},
			},
		},
	}

	for _, tc := range testcases {
		t.Run(tc.label, func(t *testing.T) {
			var timeRange *TimeRange
			var err error

			if tc.location == time.UTC {
				timeRange, err = NewUTCTimeRange(tc.start, tc.end, tc.weekdays)
			} else {
				timeRange, err = NewTimeRangeInLocation(tc.start, tc.end, tc.weekdays, tc.location)
			}
			assert.Nil(t, err)

			for _, example := range tc.examples {
				t.Run(example.label, func(t *testing.T) {
					assert.Equal(t, example.expected, timeRange.IsInSameRange(example.time1, example.time2))
					assert.Equal(t, example.expected, timeRange.IsInSameRange(example.time2, example.time1))
				})
			}
		})
	}

}

func TestTimeRangeIsInSameRangeWithDay(t *testing.T) {
	testcases := []struct {
		label     string
		startTime TimeOfDay
		endTime   TimeOfDay
		startDay  time.Weekday
		endDay    time.Weekday
		location  *time.Location
		cases     []struct {
			label    string
			time1    time.Time
			time2    time.Time
			expected bool
		}
	}{
		{
			label:     "base example",
			startTime: NewTimeOfDay(3, 0, 0),
			endTime:   NewTimeOfDay(18, 0, 0),
			startDay:  time.Monday,
			endDay:    time.Thursday,
			location:  time.UTC,
			cases: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "time1 before day range",
					time1:    time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC), // Tuesday
					time2:    time.Date(2004, time.July, 25, 3, 0, 0, 0, time.UTC), // Sunday
					expected: false,
				},
				{
					label:    "time1 after day range",
					time1:    time.Date(2004, time.July, 31, 3, 0, 0, 0, time.UTC), // Saturday
					time2:    time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC), // Tuesday
					expected: false,
				},
				{
					label:    "same date/time",
					time1:    time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC), // Tuesday
					time2:    time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC), // Tuesday
					expected: true,
				},
				{
					label:    "different days within day range",
					time1:    time.Date(2004, time.July, 26, 10, 0, 0, 0, time.UTC), // Monday
					time2:    time.Date(2004, time.July, 27, 3, 0, 0, 0, time.UTC),  // Tuesday
					expected: true,
				},
				{
					label:    "different days within day range 2",
					time1:    time.Date(2004, time.July, 27, 10, 0, 0, 0, time.UTC), // Tuesday
					time2:    time.Date(2004, time.July, 29, 2, 0, 0, 0, time.UTC),  // Thursday
					expected: true,
				},
				{
					label:    "same day, different weeks",
					time1:    time.Date(2004, time.July, 27, 10, 0, 0, 0, time.UTC), // Tuesday
					time2:    time.Date(2004, time.July, 20, 3, 0, 0, 0, time.UTC),  // Tuesday
					expected: false,
				},
				{
					label:    "same day, different weeks inverted",
					time1:    time.Date(2004, time.July, 20, 3, 0, 0, 0, time.UTC),  // Tuesday
					time2:    time.Date(2004, time.July, 27, 10, 0, 0, 0, time.UTC), // Tuesday
					expected: false,
				},
				{
					label:    "different weeks again",
					time1:    time.Date(2004, time.July, 26, 2, 0, 0, 0, time.UTC), // Monday
					time2:    time.Date(2004, time.July, 19, 3, 0, 0, 0, time.UTC), // Monday
					expected: false,
				},
			},
		},
		{
			label:     "start/end time within an hour of midnight, week long session",
			startTime: NewTimeOfDay(0, 5, 0),
			endTime:   NewTimeOfDay(23, 45, 0),
			startDay:  time.Sunday,
			endDay:    time.Saturday,
			location:  time.UTC,
			cases: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "check DST change is handled when missing an hour",
					time1:    time.Date(2006, time.April, 4, 0, 0, 0, 0, time.UTC),
					time2:    time.Date(2006, time.April, 3, 1, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "check DST change is handled when gaining an hour",
					time1:    time.Date(2006, time.October, 30, 0, 0, 0, 0, time.UTC),
					time2:    time.Date(2006, time.October, 31, 1, 0, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "works across year boundary",
					time1:    time.Date(2006, time.December, 31, 10, 10, 10, 0, time.UTC),
					time2:    time.Date(2007, time.January, 1, 10, 10, 10, 0, time.UTC),
					expected: true,
				},
			},
		},
		{
			label:     "when session days are the same",
			startTime: NewTimeOfDay(9, 1, 0),
			endTime:   NewTimeOfDay(8, 59, 0),
			location:  time.UTC,
			cases: []struct {
				label    string
				time1    time.Time
				time2    time.Time
				expected bool
			}{
				{
					label:    "same time",
					time1:    time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
					time2:    time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
					expected: true,
				},
				{
					label:    "different week",
					time1:    time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
					time2:    time.Date(2006, time.December, 10, 9, 1, 0, 0, time.UTC),
					expected: false,
				},
				{
					label:    "different day",
					time1:    time.Date(2006, time.December, 3, 9, 1, 0, 0, time.UTC),
					time2:    time.Date(2006, time.December, 4, 9, 1, 0, 0, time.UTC),
					expected: true,
				},
			},
		},
	}

	for _, tc := range testcases {
		t.Run(tc.label, func(t *testing.T) {
			var timeRange *TimeRange
			var err error

			if tc.location == time.UTC {
				timeRange, err = NewUTCWeekRange(tc.startTime, tc.endTime, tc.startDay, tc.endDay)
			} else {
				timeRange, err = NewWeekRangeInLocation(tc.startTime, tc.endTime, tc.startDay, tc.endDay, tc.location)
			}
			assert.Nil(t, err)

			for _, c := range tc.cases {
				t.Run(c.label, func(t *testing.T) {
					assert.Equal(t, c.expected, timeRange.IsInSameRange(c.time1, c.time2))
				})
			}
		})
	}
}

func TestTimeRangeIsInSameRangeWhenNil(t *testing.T) {
	time1 := time.Date(2016, time.August, 10, 2, 1, 0, 0, time.UTC)
	time2 := time.Date(2016, time.August, 10, 2, 1, 0, 0, time.UTC)

	var tr *TimeRange
	assert.True(t, tr.IsInSameRange(time1, time2), "always in same range if time range is nil")
}
