├── .github └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── docs └── interval-relations.svg ├── go.mod ├── go.sum ├── rampart.go ├── rampart_test.go └── relation.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-go@v3 16 | with: 17 | go-version: 1.18 18 | - name: Run test with coverage 19 | run: go test -v -race -covermode=atomic -coverprofile=coverage.txt ./... 20 | - name: Upload coverage to Codecov 21 | uses: codecov/codecov-action@v4 22 | with: 23 | flags: unittests 24 | fail_ci_if_error: true 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Daniel Francesconi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-rampart 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/francesconi/go-rampart.svg)](https://pkg.go.dev/github.com/francesconi/go-rampart) 4 | ![github.com/francesconi/go-rampart](https://github.com/francesconi/go-rampart/workflows/test/badge.svg) 5 | [![Codecov](https://codecov.io/gh/francesconi/go-rampart/branch/main/graph/badge.svg?token=Y1G145SZB2)](https://codecov.io/gh/francesconi/go-rampart) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/francesconi/go-rampart)](https://goreportcard.com/report/github.com/francesconi/go-rampart) 7 | 8 | Go port of the [Haskell Rampart library](https://github.com/tfausak/rampart) by [Taylor Fausak](https://taylor.fausak.me/2020/03/13/relate-intervals-with-rampart). 9 | 10 | This package provides types and functions for defining intervals and determining how they relate to each other. This can be useful to determine if an event happened during a certain time frame, or if two time frames overlap (and if so, how exactly they overlap). 11 | 12 | ## Install 13 | 14 | ```sh 15 | go get github.com/francesconi/go-rampart 16 | ``` 17 | 18 | ## Examples 19 | 20 | ### Ordered types 21 | 22 | Any type that supports the operators `<` `<=` `>=` `>`. 23 | 24 | ```go 25 | a := rampart.NewInterval(2, 3) 26 | b := rampart.NewInterval(3, 7) 27 | rel := a.Relate(b) 28 | // rel: RelationMeets 29 | ``` 30 | 31 | ### Any other type 32 | 33 | `NewIntervalFunc` accepts any type as long as a comparison function is provided. The comparison function should return values as follows: 34 | 35 | ```go 36 | cmp(t1, t2) < 0 if t1 < t2 37 | cmp(t1, t2) > 0 if t1 > t2 38 | cmp(t1, t2) == 0 if t1 == t2 39 | ``` 40 | 41 | #### time.Time 42 | 43 | ```go 44 | func compareTimes(t1, t2 time.Time) int { 45 | return int(t1.Sub(t2)) 46 | } 47 | 48 | a := rampart.NewIntervalFunc( 49 | time.Date(2022, time.April, 1, 0, 0, 0, 0, time.UTC), 50 | time.Date(2022, time.April, 8, 0, 0, 0, 0, time.UTC), 51 | compareTimes, 52 | ) 53 | b := rampart.NewIntervalFunc( 54 | time.Date(2022, time.April, 6, 0, 0, 0, 0, time.UTC), 55 | time.Date(2022, time.April, 15, 0, 0, 0, 0, time.UTC), 56 | compareTimes, 57 | ) 58 | rel := a.Relate(b) 59 | // rel: RelationOverlaps 60 | ``` 61 | 62 | ![][interval relations] 63 | 64 | [interval relations]: ./docs/interval-relations.svg 65 | -------------------------------------------------------------------------------- /docs/interval-relations.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | x before y 44 | x meets y 45 | x overlaps y 46 | x finished by y 47 | x contains y 48 | x starts y 49 | x equal y 50 | x started by y 51 | x during y 52 | x finishes y 53 | x overlapped by y 54 | x met by y 55 | x after y 56 | 57 | 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/francesconi/go-rampart 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= 2 | golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= 3 | -------------------------------------------------------------------------------- /rampart.go: -------------------------------------------------------------------------------- 1 | package rampart 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | // Interval represents two values, the lesser and the greater. 6 | // Both must be of the same type. 7 | type Interval[T any] struct { 8 | x, y T 9 | cmp func(T, T) int 10 | } 11 | 12 | // NewIntervalFunc returns an Interval out of x and y so that the Interval 13 | // can be sorted on construction by the given comparison function. 14 | // 15 | // The comparison function should return values as follows: 16 | // 17 | // cmp(t1, t2) < 0 if t1 < t2 18 | // cmp(t1, t2) > 0 if t1 > t2 19 | // cmp(t1, t2) == 0 if t1 == t2 20 | // 21 | // For example, to compare time.Time instances, 22 | // 23 | // NewIntervalFunc(t1, t2, func(t1, t2 time.Time) int { return int(t1.Sub(t2)) }) 24 | func NewIntervalFunc[T any](x, y T, cmp func(T, T) int) Interval[T] { 25 | if cmp(x, y) < 0 { 26 | return Interval[T]{x, y, cmp} 27 | } 28 | return Interval[T]{y, x, cmp} 29 | } 30 | 31 | // NewInterval returns an Interval that uses the natural ordering of T for 32 | // comparison. 33 | func NewInterval[T constraints.Ordered](x, y T) Interval[T] { 34 | // The entire comparison function could be just 35 | // return t1 - t2 36 | // but strings are ordered, and subtraction doesn't make sense for 37 | // them, so it has to be done manually. 38 | return NewIntervalFunc(x, y, func(t1, t2 T) int { 39 | if t1 < t2 { 40 | return -1 41 | } 42 | if t1 == t2 { 43 | return 0 44 | } 45 | return 1 46 | }) 47 | } 48 | 49 | // Lesser returns the lesser value from an Interval. 50 | func (i Interval[T]) Lesser() T { 51 | return i.x 52 | } 53 | 54 | // Greater returns the greater value from an Interval. 55 | func (i Interval[T]) Greater() T { 56 | return i.y 57 | } 58 | 59 | // IsEmpty returns true if the given Interval is empty, false otherwise. 60 | // An Interval is empty if its lesser equals its greater. 61 | func (i Interval[T]) IsEmpty() bool { 62 | return i.cmp(i.x, i.y) == 0 63 | } 64 | 65 | // IsNonEmpty returns true if the given Interval is non-empty, false otherwise. 66 | // An Interval is non-empty if its lesser is not equal to its greater. 67 | func (i Interval[T]) IsNonEmpty() bool { 68 | return !i.IsEmpty() 69 | } 70 | 71 | // Relates tells you how Interval x relates to Interval y. 72 | // Consult the Relation documentation for an explanation 73 | // of all the possible results. 74 | func (x Interval[T]) Relate(y Interval[T]) Relation { 75 | lxly := x.cmp(x.Lesser(), y.Lesser()) 76 | lxgy := x.cmp(x.Lesser(), y.Greater()) 77 | gxly := x.cmp(x.Greater(), y.Lesser()) 78 | gxgy := x.cmp(x.Greater(), y.Greater()) 79 | switch { 80 | case lxly == 0 && gxgy == 0: 81 | return RelationEqual 82 | case gxly < 0: 83 | return RelationBefore 84 | case lxly < 0 && gxly == 0 && gxgy < 0: 85 | return RelationMeets 86 | case gxly == 0: 87 | return RelationOverlaps 88 | case lxly > 0 && lxgy == 0 && gxgy > 0: 89 | return RelationMetBy 90 | case lxgy == 0: 91 | return RelationOverlappedBy 92 | case lxgy > 0: 93 | return RelationAfter 94 | case lxly < 0 && gxgy < 0: 95 | return RelationOverlaps 96 | case lxly < 0 && gxgy == 0: 97 | return RelationFinishedBy 98 | case lxly < 0 && gxgy > 0: 99 | return RelationContains 100 | case lxly == 0 && gxgy < 0: 101 | return RelationStarts 102 | case lxly == 0 && gxgy > 0: 103 | return RelationStartedBy 104 | case lxly > 0 && gxgy < 0: 105 | return RelationDuring 106 | case lxly > 0 && gxgy == 0: 107 | return RelationFinishes 108 | case lxly > 0 && gxgy > 0: 109 | return RelationOverlappedBy 110 | default: 111 | return RelationUnknown 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /rampart_test.go: -------------------------------------------------------------------------------- 1 | package rampart 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestTimeInterval(t *testing.T) { 10 | now := time.Now() 11 | a := NewIntervalFunc(now.Add(1000), now, func(t1, t2 time.Time) int { return int(t1.Sub(t2)) }) 12 | requireEqual(t, now, a.Lesser()) 13 | } 14 | 15 | func TestNewInterval(t *testing.T) { 16 | t.Run("sorts the tuple", func(t *testing.T) { 17 | a := NewInterval("a", "b") 18 | b := NewInterval("b", "a") 19 | requireEqual(t, a.x, b.x) 20 | requireEqual(t, a.y, b.y) 21 | }) 22 | } 23 | 24 | func TestLesser(t *testing.T) { 25 | t.Run("returns the lesser element", func(t *testing.T) { 26 | requireEqual(t, "a", NewInterval("a", "b").Lesser()) 27 | }) 28 | } 29 | 30 | func TestGreater(t *testing.T) { 31 | t.Run("returns the greater element", func(t *testing.T) { 32 | requireEqual(t, "b", NewInterval("a", "b").Greater()) 33 | }) 34 | } 35 | 36 | func TestIsEmpty(t *testing.T) { 37 | t.Run("returns true when the interval is empty", func(t *testing.T) { 38 | requireEqual(t, true, NewInterval("a", "a").IsEmpty()) 39 | }) 40 | t.Run("returns false when the interval is non-empty", func(t *testing.T) { 41 | requireEqual(t, false, NewInterval("a", "b").IsEmpty()) 42 | }) 43 | } 44 | 45 | func TestIsNonEmpty(t *testing.T) { 46 | t.Run("returns false when the interval is empty", func(t *testing.T) { 47 | requireEqual(t, false, NewInterval("a", "a").IsNonEmpty()) 48 | }) 49 | t.Run("returns true when the interval is non-empty", func(t *testing.T) { 50 | requireEqual(t, true, NewInterval("a", "b").IsNonEmpty()) 51 | }) 52 | } 53 | 54 | func relate(x1, y1 int) func(int, int) Relation { 55 | return func(x2, y2 int) Relation { 56 | return NewInterval(x1, y1).Relate(NewInterval(x2, y2)) 57 | } 58 | } 59 | 60 | func TestRelate(t *testing.T) { 61 | t.Run("identifies the before relation", func(t *testing.T) { 62 | requireEqual(t, RelationBefore, relate(1, 2)(3, 7)) 63 | }) 64 | t.Run("identifies the meets relation", func(t *testing.T) { 65 | requireEqual(t, RelationMeets, relate(2, 3)(3, 7)) 66 | }) 67 | t.Run("identifies the overlaps relation", func(t *testing.T) { 68 | requireEqual(t, RelationOverlaps, relate(2, 4)(3, 7)) 69 | }) 70 | t.Run("identifies the finished by relation", func(t *testing.T) { 71 | requireEqual(t, RelationFinishedBy, relate(2, 7)(3, 7)) 72 | }) 73 | t.Run("identifies the contains relation", func(t *testing.T) { 74 | requireEqual(t, RelationContains, relate(2, 8)(3, 7)) 75 | }) 76 | t.Run("identifies the starts relation", func(t *testing.T) { 77 | requireEqual(t, RelationStarts, relate(3, 4)(3, 7)) 78 | }) 79 | t.Run("identifies the equal relation", func(t *testing.T) { 80 | requireEqual(t, RelationEqual, relate(3, 7)(3, 7)) 81 | }) 82 | t.Run("identifies the started by relation", func(t *testing.T) { 83 | requireEqual(t, RelationStartedBy, relate(3, 8)(3, 7)) 84 | }) 85 | t.Run("identifies the during relation", func(t *testing.T) { 86 | requireEqual(t, RelationDuring, relate(4, 6)(3, 7)) 87 | }) 88 | t.Run("identifies the finishes relation", func(t *testing.T) { 89 | requireEqual(t, RelationFinishes, relate(6, 7)(3, 7)) 90 | }) 91 | t.Run("identifies the overlapped by relation", func(t *testing.T) { 92 | requireEqual(t, RelationOverlappedBy, relate(6, 8)(3, 7)) 93 | }) 94 | t.Run("identifies the met by relation", func(t *testing.T) { 95 | requireEqual(t, RelationMetBy, relate(7, 8)(3, 7)) 96 | }) 97 | t.Run("identifies the after relation", func(t *testing.T) { 98 | requireEqual(t, RelationAfter, relate(8, 9)(3, 7)) 99 | }) 100 | 101 | t.Run("empty left interval", func(t *testing.T) { 102 | t.Run("before", func(t *testing.T) { 103 | requireEqual(t, RelationBefore, relate(2, 2)(3, 7)) 104 | }) 105 | t.Run("at lesser", func(t *testing.T) { 106 | requireEqual(t, RelationOverlaps, relate(3, 3)(3, 7)) 107 | }) 108 | t.Run("during", func(t *testing.T) { 109 | requireEqual(t, RelationDuring, relate(5, 5)(3, 7)) 110 | }) 111 | t.Run("at greater", func(t *testing.T) { 112 | requireEqual(t, RelationOverlappedBy, relate(7, 7)(3, 7)) 113 | }) 114 | t.Run("after", func(t *testing.T) { 115 | requireEqual(t, RelationAfter, relate(8, 8)(3, 7)) 116 | }) 117 | }) 118 | 119 | t.Run("empty right interval", func(t *testing.T) { 120 | t.Run("before", func(t *testing.T) { 121 | requireEqual(t, RelationAfter, relate(3, 7)(2, 2)) 122 | }) 123 | t.Run("at lesser", func(t *testing.T) { 124 | requireEqual(t, RelationOverlappedBy, relate(3, 7)(3, 3)) 125 | }) 126 | t.Run("during", func(t *testing.T) { 127 | requireEqual(t, RelationContains, relate(3, 7)(5, 5)) 128 | }) 129 | t.Run("at greater", func(t *testing.T) { 130 | requireEqual(t, RelationOverlaps, relate(3, 7)(7, 7)) 131 | }) 132 | t.Run("after", func(t *testing.T) { 133 | requireEqual(t, RelationBefore, relate(3, 7)(8, 8)) 134 | }) 135 | }) 136 | 137 | t.Run("both empty intervals", func(t *testing.T) { 138 | t.Run("before", func(t *testing.T) { 139 | requireEqual(t, RelationBefore, relate(4, 4)(5, 5)) 140 | }) 141 | t.Run("equal", func(t *testing.T) { 142 | requireEqual(t, RelationEqual, relate(5, 5)(5, 5)) 143 | }) 144 | t.Run("after", func(t *testing.T) { 145 | requireEqual(t, RelationAfter, relate(6, 6)(5, 5)) 146 | }) 147 | }) 148 | } 149 | 150 | func TestInvert(t *testing.T) { 151 | t.Run("inverts the after relation", func(t *testing.T) { 152 | requireEqual(t, RelationBefore, RelationAfter.Invert()) 153 | }) 154 | t.Run("inverts the before relation", func(t *testing.T) { 155 | requireEqual(t, RelationAfter, RelationBefore.Invert()) 156 | }) 157 | t.Run("inverts the contains relation", func(t *testing.T) { 158 | requireEqual(t, RelationDuring, RelationContains.Invert()) 159 | }) 160 | t.Run("inverts the during relation", func(t *testing.T) { 161 | requireEqual(t, RelationContains, RelationDuring.Invert()) 162 | }) 163 | t.Run("inverts the equal relation", func(t *testing.T) { 164 | requireEqual(t, RelationEqual, RelationEqual.Invert()) 165 | }) 166 | t.Run("inverts the finished by relation", func(t *testing.T) { 167 | requireEqual(t, RelationFinishes, RelationFinishedBy.Invert()) 168 | }) 169 | t.Run("inverts the finishes relation", func(t *testing.T) { 170 | requireEqual(t, RelationFinishedBy, RelationFinishes.Invert()) 171 | }) 172 | t.Run("inverts the meets relation", func(t *testing.T) { 173 | requireEqual(t, RelationMetBy, RelationMeets.Invert()) 174 | }) 175 | t.Run("inverts the met by relation", func(t *testing.T) { 176 | requireEqual(t, RelationMeets, RelationMetBy.Invert()) 177 | }) 178 | t.Run("inverts the overlapped by relation", func(t *testing.T) { 179 | requireEqual(t, RelationOverlaps, RelationOverlappedBy.Invert()) 180 | }) 181 | t.Run("inverts the overlaps relation", func(t *testing.T) { 182 | requireEqual(t, RelationOverlappedBy, RelationOverlaps.Invert()) 183 | }) 184 | t.Run("inverts the started by relation", func(t *testing.T) { 185 | requireEqual(t, RelationStarts, RelationStartedBy.Invert()) 186 | }) 187 | t.Run("inverts the starts relation", func(t *testing.T) { 188 | requireEqual(t, RelationStartedBy, RelationStarts.Invert()) 189 | }) 190 | t.Run("inverts an invalid relation", func(t *testing.T) { 191 | requireEqual(t, RelationUnknown, Relation(42).Invert()) 192 | }) 193 | } 194 | 195 | func requireEqual(t *testing.T, expected, actual any) { 196 | _, file, line, ok := runtime.Caller(1) 197 | if !ok { 198 | return 199 | } 200 | 201 | if actual != expected { 202 | t.Errorf("\nTrace:\t%s:%d"+ 203 | "\nError:\texpected %v, got %v"+ 204 | "\nTest:\t%s", file, line, actual, expected, t.Name()) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /relation.go: -------------------------------------------------------------------------------- 1 | package rampart 2 | 3 | // Relation represents how two Intervals relate to each other. 4 | type Relation int 5 | 6 | const ( 7 | RelationUnknown Relation = iota 8 | 9 | /* 10 | Interval x is before Interval y. 11 | 12 | +---+ 13 | | x | 14 | +---+ 15 | +---+ 16 | | y | 17 | +---+ 18 | */ 19 | RelationBefore 20 | 21 | /* 22 | Interval x meets Interval y. 23 | 24 | +---+ 25 | | x | 26 | +---+ 27 | +---+ 28 | | y | 29 | +---+ 30 | */ 31 | RelationMeets 32 | 33 | /* 34 | Interval x overlaps Interval y. 35 | 36 | +---+ 37 | | x | 38 | +---+ 39 | +---+ 40 | | y | 41 | +---+ 42 | */ 43 | RelationOverlaps 44 | 45 | /* 46 | Interval x is finished by Interval y. 47 | 48 | +-----+ 49 | | x | 50 | +-----+ 51 | +---+ 52 | | y | 53 | +---+ 54 | */ 55 | RelationFinishedBy 56 | 57 | /* 58 | Interval x contains Interval y. 59 | 60 | +-------+ 61 | | x | 62 | +-------+ 63 | +---+ 64 | | y | 65 | +---+ 66 | */ 67 | RelationContains 68 | 69 | /* 70 | Interval x starts Interval y. 71 | 72 | +---+ 73 | | x | 74 | +---+ 75 | +-----+ 76 | | y | 77 | +-----+ 78 | */ 79 | RelationStarts 80 | 81 | /* 82 | Interval x is equal to Interval y. 83 | 84 | +---+ 85 | | x | 86 | +---+ 87 | +---+ 88 | | y | 89 | +---+ 90 | */ 91 | RelationEqual 92 | 93 | /* 94 | Interval x is started by Interval y. 95 | 96 | +-----+ 97 | | x | 98 | +-----+ 99 | +---+ 100 | | y | 101 | +---+ 102 | */ 103 | RelationStartedBy 104 | 105 | /* 106 | Interval x is during Interval y. 107 | 108 | +---+ 109 | | x | 110 | +---+ 111 | +-------+ 112 | | y | 113 | +-------+ 114 | */ 115 | RelationDuring 116 | 117 | /* 118 | Interval x finishes Interval y. 119 | 120 | +---+ 121 | | x | 122 | +---+ 123 | +-----+ 124 | | y | 125 | +-----+ 126 | */ 127 | RelationFinishes 128 | 129 | /* 130 | Interval x is overlapped by Interval y. 131 | 132 | +---+ 133 | | x | 134 | +---+ 135 | +---+ 136 | | y | 137 | +---+ 138 | */ 139 | RelationOverlappedBy 140 | 141 | /* 142 | Interval x is met by Interval y. 143 | 144 | +---+ 145 | | x | 146 | +---+ 147 | +---+ 148 | | y | 149 | +---+ 150 | */ 151 | RelationMetBy 152 | 153 | /* 154 | Interval x is after Interval y. 155 | 156 | +---+ 157 | | x | 158 | +---+ 159 | +---+ 160 | | y | 161 | +---+ 162 | */ 163 | RelationAfter 164 | ) 165 | 166 | // Inverts a Relation. Every Relation has an inverse. 167 | func (r Relation) Invert() Relation { 168 | switch r { 169 | case RelationAfter: 170 | return RelationBefore 171 | case RelationBefore: 172 | return RelationAfter 173 | case RelationContains: 174 | return RelationDuring 175 | case RelationDuring: 176 | return RelationContains 177 | case RelationEqual: 178 | return RelationEqual 179 | case RelationFinishedBy: 180 | return RelationFinishes 181 | case RelationFinishes: 182 | return RelationFinishedBy 183 | case RelationMeets: 184 | return RelationMetBy 185 | case RelationMetBy: 186 | return RelationMeets 187 | case RelationOverlappedBy: 188 | return RelationOverlaps 189 | case RelationOverlaps: 190 | return RelationOverlappedBy 191 | case RelationStartedBy: 192 | return RelationStarts 193 | case RelationStarts: 194 | return RelationStartedBy 195 | default: 196 | return RelationUnknown 197 | } 198 | } 199 | --------------------------------------------------------------------------------