diff --git a/.gitignore b/.gitignore index 7de7715..77bb0b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor .devcontainer +.DS_Store diff --git a/v2/date_time_range.go b/v2/date_time_range.go index b8c7dc4..d73a7eb 100644 --- a/v2/date_time_range.go +++ b/v2/date_time_range.go @@ -94,3 +94,18 @@ func formatNumberRangeToDateRange(numberRange NumberRange[int64]) DateTimeRange } return dateTimeRange } + +func (r DateTimeRange) Difference(r1 DateTimeRange) []DateTimeRange { + var differenceResult = diff( + r.formatToNumberRange(), + r1.formatToNumberRange(), + ) + if len(differenceResult) == 0 { + return nil + } + diffDateTimeRange := make([]DateTimeRange, 0) + for _, rangeNumber := range differenceResult { + diffDateTimeRange = append(diffDateTimeRange, formatNumberRangeToDateRange(rangeNumber)) + } + return diffDateTimeRange +} diff --git a/v2/date_time_range_test.go b/v2/date_time_range_test.go index 377608c..3ef5e2b 100644 --- a/v2/date_time_range_test.go +++ b/v2/date_time_range_test.go @@ -150,3 +150,75 @@ func TestExclusiveIntersectionForTimeRange(t *testing.T) { assert.True(t, expectedIntersectionRange[1].IsExclusive) }) } + +func TestDifferenceForTimeRange(t *testing.T) { + t.Run("expect difference between same ranges with one of the bounds is exclusive", func(t *testing.T) { + now := time.Now() + range1 := DateTimeRange{ + {Value: now.Add(-10 * time.Hour).Truncate(time.Minute)}, + {Value: now.Add(-5 * time.Hour).Truncate(time.Minute), IsExclusive: true}, + } + range2 := DateTimeRange{ + {Value: now.Add(-10 * time.Hour).Truncate(time.Minute)}, + {Value: now.Add(-5 * time.Hour).Truncate(time.Minute)}, + } + diffRange := range1.Difference(range2) + assert.Equal(t, 1, len(diffRange)) + assert.True(t, diffRange[0][0].Value.Equal(range2[1].Value)) + assert.True(t, diffRange[0][1].Value.Equal(range2[1].Value)) + }) + + t.Run("expect difference with second range includes in first range", func(t *testing.T) { + // 1. -------------- + // 2. ------ + range1 := DateTimeRange{ + {Value: time.Now().Add(-10 * time.Hour).Truncate(time.Minute)}, + {Value: time.Now().Add(-5 * time.Hour).Truncate(time.Minute)}, + } + range2 := DateTimeRange{ + {Value: time.Now().Add(-7 * time.Hour).Truncate(time.Minute)}, + {Value: time.Now().Add(-6 * time.Hour).Truncate(time.Minute)}, + } + diffRange := range1.Difference(range2) + expectedDifferenceRange := []DateTimeRange{ + {range1[0], range2[0]}, + {range2[1], range1[1]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.True(t, diffRange[0][0].Value.Equal(expectedDifferenceRange[0][0].Value)) + assert.True(t, diffRange[0][1].Value.Equal(expectedDifferenceRange[0][1].Value)) + assert.True(t, diffRange[1][0].Value.Equal(expectedDifferenceRange[1][0].Value)) + assert.True(t, diffRange[1][1].Value.Equal(expectedDifferenceRange[1][1].Value)) + + diffRange = range2.Difference(range1) + assert.Equal(t, 0, len(diffRange)) + }) + + t.Run("expect difference with 2 ranges in intersection", func(t *testing.T) { + // 1. -------------- + // 2. ------ + range1 := DateTimeRange{ + {Value: time.Now().Add(-10 * time.Hour).Truncate(time.Minute)}, + {Value: time.Now().Add(-6 * time.Hour).Truncate(time.Minute)}, + } + range2 := DateTimeRange{ + {Value: time.Now().Add(-7 * time.Hour).Truncate(time.Minute)}, + {Value: time.Now().Add(-3 * time.Hour).Truncate(time.Minute)}, + } + diffRange := range1.Difference(range2) + expectedDifferenceRange := []DateTimeRange{ + {range1[0], range2[0]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.True(t, diffRange[0][0].Value.Equal(expectedDifferenceRange[0][0].Value)) + assert.True(t, diffRange[0][1].Value.Equal(expectedDifferenceRange[0][1].Value)) + + diffRange = range2.Difference(range1) + expectedDifferenceRange = []DateTimeRange{ + {range1[1], range2[1]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + }) +} diff --git a/v2/diff.go b/v2/diff.go new file mode 100644 index 0000000..5bb63be --- /dev/null +++ b/v2/diff.go @@ -0,0 +1,45 @@ +package grange + +func diff[K Number, L NumberRange[K]](r L, r1 L) []NumberRange[K] { + // return substract dateRange2 into dateRange1 + // if no intersection, the dateRange1 is returneed + start1 := r[0] + end1 := r[1] + start2 := r1[0] + end2 := r1[1] + + if end1.Value < start2.Value || end2.Value < start1.Value { + return []NumberRange[K]{ + {start1, end1}, + } + } + + ranges := make([]NumberRange[K], 0) + if start1.Value < start2.Value && start2.Value < end1.Value { + ranges = append(ranges, NumberRange[K]{start1, start2}) + } + + if start1.Value == start2.Value && (start2.IsExclusive != start1.IsExclusive) { + if !start1.IsExclusive { + ranges = append(ranges, NumberRange[K]{start1, start1}) + } else { + ranges = append(ranges, NumberRange[K]{start2, start2}) + + } + } + + if end1.Value == end2.Value && (end2.IsExclusive != end1.IsExclusive) { + if !end1.IsExclusive { + ranges = append(ranges, NumberRange[K]{end1, end1}) + } else { + ranges = append(ranges, NumberRange[K]{end2, end2}) + + } + } + + if end2.Value < end1.Value && end2.Value > start1.Value && end2.Value != end1.Value { + ranges = append(ranges, NumberRange[K]{end2, end1}) + } + + return ranges +} diff --git a/v2/number_range.go b/v2/number_range.go index d2152b8..19dd499 100644 --- a/v2/number_range.go +++ b/v2/number_range.go @@ -35,3 +35,7 @@ func formatEmptyExclusiveRange[K Number](r NumberRange[K]) *NumberRange[K] { } return &r } + +func (r NumberRange[K]) Difference(r1 NumberRange[K]) []NumberRange[K] { + return diff(r, r1) +} diff --git a/v2/number_range_test.go b/v2/number_range_test.go index c4fcf44..3f3e93f 100644 --- a/v2/number_range_test.go +++ b/v2/number_range_test.go @@ -106,3 +106,81 @@ func TestExclusiveIntersectionForNumberRange(t *testing.T) { assert.Nil(t, intersectionRange) }) } + +func TestDifferenceForNumberRange(t *testing.T) { + t.Run("expect difference between same ranges with one of the bounds is exclusive", func(t *testing.T) { + range1 := NumberRange[int]{ + {Value: 0}, {Value: 20}, + } + range2 := NumberRange[int]{ + {Value: 0}, {Value: 20, IsExclusive: true}, + } + diffRange := range1.Difference(range2) + expectedDifferenceRange := []NumberRange[int]{ + {range1[1], range1[1]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + assert.False(t, diffRange[0][0].IsExclusive) + assert.False(t, diffRange[0][1].IsExclusive) + + diffRange = range2.Difference(range1) + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + assert.False(t, diffRange[0][0].IsExclusive) + assert.False(t, diffRange[0][1].IsExclusive) + }) + + t.Run("expect difference with second range includes in first range", func(t *testing.T) { + // 1. -------------- + // 2. ------ + range1 := NumberRange[int]{ + {Value: 0}, {Value: 20}, + } + range2 := NumberRange[int]{ + {Value: 10}, {Value: 15}, + } + diffRange := range1.Difference(range2) + expectedDifferenceRange := []NumberRange[int]{ + {range1[0], range2[0]}, + {range2[1], range1[1]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + assert.Equal(t, diffRange[1][0].Value, expectedDifferenceRange[1][0].Value) + assert.Equal(t, diffRange[1][1].Value, expectedDifferenceRange[1][1].Value) + + diffRange = range2.Difference(range1) + assert.Equal(t, 0, len(diffRange)) + }) + + t.Run("expect difference with 2 ranges in intersection", func(t *testing.T) { + // 1. -------------- + // 2. ------ + range1 := NumberRange[int]{ + {Value: 0}, {Value: 20}, + } + range2 := NumberRange[int]{ + {Value: 10}, {Value: 30}, + } + diffRange := range1.Difference(range2) + expectedDifferenceRange := []NumberRange[int]{ + {range1[0], range2[0]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + + diffRange = range2.Difference(range1) + expectedDifferenceRange = []NumberRange[int]{ + {range1[1], range2[1]}, + } + assert.Equal(t, len(diffRange), len(expectedDifferenceRange)) + assert.Equal(t, diffRange[0][0].Value, expectedDifferenceRange[0][0].Value) + assert.Equal(t, diffRange[0][1].Value, expectedDifferenceRange[0][1].Value) + }) + +} diff --git a/v2/range.go b/v2/range.go index f147ae8..2bfd0e3 100644 --- a/v2/range.go +++ b/v2/range.go @@ -26,6 +26,7 @@ type RangeTypes interface { type RangeInterface[K RangeTypes] interface { Intersection(K) *K + Difference(K) []K IsEmpty() bool ToPostgresString() (string, string, string) // return lowerBound, upperBound, inclusivity/exclusivity of bounds for postgres } @@ -33,3 +34,7 @@ type RangeInterface[K RangeTypes] interface { func Intersection[K RangeTypes](t RangeInterface[K], t1 K) *K { return t.Intersection(t1) } + +func Difference[K RangeTypes](t RangeInterface[K], t1 K) []K { + return t.Difference(t1) +} diff --git a/v2/range_test.go b/v2/range_test.go index 9fdecbc..67c8332 100644 --- a/v2/range_test.go +++ b/v2/range_test.go @@ -40,3 +40,19 @@ func TestIntersectionForRangeInterfaceWithNumberType(t *testing.T) { assert.Equal(t, expectedIntersectionRange[0].Value, intersectionRange[0].Value) assert.Equal(t, expectedIntersectionRange[1].Value, intersectionRange[1].Value) } + +func TestDifferenceForRangeInterfaceWithNumberType(t *testing.T) { + range1 := NumberRange[int]{ + {Value: 0}, {Value: 20}, + } + range2 := NumberRange[int]{ + {Value: -10}, {Value: 10}, + } + differenceRange := Difference[NumberRange[int]](range1, range2) + expectedDifferenceRange := []NumberRange[int]{ + {range2[1], + range1[1]}, + } + assert.Equal(t, expectedDifferenceRange[0][0].Value, differenceRange[0][0].Value) + assert.Equal(t, expectedDifferenceRange[0][1].Value, differenceRange[0][1].Value) +}