mirror of
https://github.com/taigrr/most-specific-period.git
synced 2026-04-02 03:38:41 -07:00
Compare commits
16 Commits
v1.0.0
...
cd/lint-fi
| Author | SHA1 | Date | |
|---|---|---|---|
| 523d062b04 | |||
| f3bf5c7d6b | |||
| 8b431b5d2f | |||
|
433d28a87b
|
|||
|
2e68bb06af
|
|||
|
f989049717
|
|||
|
|
6adb6be6b4 | ||
|
|
dd79d7a54b | ||
| 9317c4b137 | |||
|
|
a2c1864a77 | ||
|
|
8612f90d46 | ||
|
|
9698d90308 | ||
|
88ce350a61
|
|||
|
0f88864200
|
|||
|
71bb0676c0
|
|||
|
01897a6ae8
|
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: taigrr # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
15
README.md
15
README.md
@@ -8,7 +8,14 @@ An MSP, or Most Specific Period is defined as follows:
|
||||
- Given two valid periods, each exactly 30 days long, but offset by a week, the second (newer) period is given precedence.
|
||||
- Given two valid periods with exact same start and end time, the period with the lowest-ranking lexicographic identifier is returned (i.e. period B has precedence over period A, as ‘B’ comes after ‘A’ lexicographically.) This is because the default behavior is to choose the newer period, and named periods are often in lexicographical order (increasing numbers, letters, etc.)
|
||||
|
||||
This library operates off Period structs, which contain the following:
|
||||
- StartTime (time.Time)
|
||||
- EndTime (time.Time)
|
||||
- Identifier (string)
|
||||
This library operates off Period interfaces, which contain the following:
|
||||
|
||||
```
|
||||
type Period interface {
|
||||
GetStartTime() time.Time // Inclusive start time
|
||||
GetEndTime() time.Time // Exclusive end time ("expiration time")
|
||||
GetIdentifier() string
|
||||
}
|
||||
```
|
||||
|
||||
An example program is available to observe the operation and usage of this library.
|
||||
|
||||
33
main.go
33
main.go
@@ -20,9 +20,11 @@ type Period struct {
|
||||
func (p Period) GetEndTime() time.Time {
|
||||
return p.EndTime
|
||||
}
|
||||
|
||||
func (p Period) GetStartTime() time.Time {
|
||||
return p.StartTime
|
||||
}
|
||||
|
||||
func (p Period) GetIdentifier() string {
|
||||
return p.Identifier
|
||||
}
|
||||
@@ -33,13 +35,35 @@ func init() {
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
func warnMessage() {
|
||||
fmt.Print("Please type your date formats as follows, hit return between each field (RFC 3339), and hit Control+D to signal you are complete: \nIdentifier: id\nStartTime: 2019-10-12T07:20:50.52Z\nEndTime: 2019-10-12T07:20:50.52Z\n")
|
||||
}
|
||||
|
||||
func helpMessage() {
|
||||
fmt.Print("\nmost-specific-period [-h][-d]\n\nGenerates a timeline of periods and will provide a most specific period if available.\n\n-h\tShows this help menu\n-d\tProvide an RFC 3339 time to provide an alternate point for calculating MSP.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var start time.Time
|
||||
help := flag.Bool("h", false, "displays help command")
|
||||
userDate := flag.String("d", "", "use a custom date to calculate MSP")
|
||||
flag.Parse()
|
||||
if *help {
|
||||
helpMessage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if userDate != nil && *userDate != "" {
|
||||
t, err := time.Parse(time.RFC3339, *userDate)
|
||||
if err != nil {
|
||||
fmt.Println("Please enter the date using the YYYY-MM-DDT00:00:00.00Z")
|
||||
os.Exit(1)
|
||||
}
|
||||
start = t
|
||||
} else {
|
||||
start = time.Now()
|
||||
}
|
||||
terminal := false
|
||||
fi, _ := os.Stdin.Stat()
|
||||
if (fi.Mode() & os.ModeCharDevice) == 0 {
|
||||
@@ -98,9 +122,14 @@ func main() {
|
||||
count++
|
||||
}
|
||||
|
||||
m, err := msp.MostSpecificPeriod(time.Now(), periods...)
|
||||
vals := msp.GenerateTimeline(periods...)
|
||||
fmt.Print("\nTimeline of changeovers:\n")
|
||||
for _, val := range vals {
|
||||
fmt.Println(val)
|
||||
}
|
||||
m, err := msp.MostSpecificPeriod(start, periods...)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
fmt.Printf("No significant period found\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if terminal {
|
||||
|
||||
60
msp/changeover.go
Normal file
60
msp/changeover.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetChangeOvers(periods ...Period) (changeovers []time.Time) {
|
||||
timeStamps := []time.Time{}
|
||||
for _, x := range periods {
|
||||
timeStamps = append(timeStamps, x.GetEndTime())
|
||||
timeStamps = append(timeStamps, x.GetStartTime())
|
||||
}
|
||||
if len(timeStamps) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(timeStamps, func(i, j int) bool {
|
||||
return timeStamps[i].Before(timeStamps[j])
|
||||
})
|
||||
// timeStamps is sorted, so this will always result in an unused time
|
||||
// struct, as it's before the first
|
||||
previousTs := timeStamps[0].Add(-10 * time.Nanosecond)
|
||||
for _, ts := range timeStamps {
|
||||
if ts.Equal(previousTs) {
|
||||
continue
|
||||
}
|
||||
previousTs = ts
|
||||
before := ts.Add(-1 * time.Nanosecond)
|
||||
after := ts.Add(1 * time.Nanosecond)
|
||||
from, _ := MostSpecificPeriod(before, periods...)
|
||||
to, _ := MostSpecificPeriod(after, periods...)
|
||||
if from == to {
|
||||
continue
|
||||
}
|
||||
changeovers = append(changeovers, ts)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetNextChangeOver(t time.Time, periods ...Period) (ts time.Time, err error) {
|
||||
changeOvers := GetChangeOvers(periods...)
|
||||
for _, ts := range changeOvers {
|
||||
if ts.After(t) {
|
||||
return ts, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, ErrNoNextChangeover
|
||||
}
|
||||
|
||||
func FlattenPeriods(periods ...Period) (ids []string) {
|
||||
changeovers := GetChangeOvers(periods...)
|
||||
for _, c := range changeovers {
|
||||
id, err := MostSpecificPeriod(c, periods...)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return
|
||||
}
|
||||
480
msp/changeover_test.go
Normal file
480
msp/changeover_test.go
Normal file
@@ -0,0 +1,480 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func slicesEqual[K comparable](x []K, y []K) bool {
|
||||
if len(x) != len(y) {
|
||||
return false
|
||||
}
|
||||
for i, w := range x {
|
||||
if w != y[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestGetChangeOvers(t *testing.T) {
|
||||
// use a static timestamp to make sure tests don't fail on slower systems or during a process pause
|
||||
now := time.Now()
|
||||
testCases := []struct {
|
||||
ts time.Time
|
||||
testID string
|
||||
result []time.Time
|
||||
periods []Period
|
||||
}{
|
||||
{
|
||||
testID: "No choices",
|
||||
ts: now,
|
||||
result: []time.Time{},
|
||||
periods: []Period{},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is second",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-5 * time.Minute), now.Add(-2 * time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one is a year, other a minute",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-1 * time.Hour * 24 * 365), now.Add(-5 * time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
testID: "Two Choices, shorter is first",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-5 * time.Minute), now.Add(-2 * time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one in the past",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-2 * time.Minute), now.Add(-time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one invalid",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-2 * time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, Identical periods",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "One choice",
|
||||
ts: now,
|
||||
result: []time.Time{now.Add(-time.Minute), now.Add(time.Minute)},
|
||||
periods: []Period{TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testID, func(t *testing.T) {
|
||||
changeovers := GetChangeOvers(tc.periods...)
|
||||
if !slicesEqual(changeovers, tc.result) {
|
||||
t.Errorf("Expected %v but got %v", tc.result, changeovers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenPeriods(t *testing.T) {
|
||||
// use a static timestamp to make sure tests don't fail on slower systems or during a process pause
|
||||
now := time.Now()
|
||||
testCases := []struct {
|
||||
ts time.Time
|
||||
testID string
|
||||
result []string
|
||||
err error
|
||||
periods []Period
|
||||
}{
|
||||
{
|
||||
testID: "No choices",
|
||||
ts: now,
|
||||
result: []string{},
|
||||
err: ErrNoValidPeriods,
|
||||
periods: []Period{},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is second",
|
||||
ts: now,
|
||||
result: []string{"A", "B"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one is a year, other a minute",
|
||||
ts: now,
|
||||
result: []string{"A", "B"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
testID: "Two Choices, shorter is first",
|
||||
ts: now,
|
||||
result: []string{"B", "A"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one in the past",
|
||||
ts: now,
|
||||
result: []string{"B", "A"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one invalid",
|
||||
ts: now,
|
||||
result: []string{"B"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, Identical periods",
|
||||
ts: now,
|
||||
result: []string{"B"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Triple Nested Periods",
|
||||
ts: now,
|
||||
result: []string{"A", "B", "C", "B", "A"},
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-15 * time.Minute),
|
||||
EndTime: now.Add(15 * time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(5 * time.Minute),
|
||||
Identifier: "C",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-10 * time.Minute),
|
||||
EndTime: now.Add(10 * time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "One choice",
|
||||
ts: now,
|
||||
result: []string{"A"},
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testID, func(t *testing.T) {
|
||||
changeovers := FlattenPeriods(tc.periods...)
|
||||
if !slicesEqual(changeovers, tc.result) {
|
||||
t.Errorf("Expected %v but got %v", tc.result, changeovers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNextChangeOver(t *testing.T) {
|
||||
// use a static timestamp to make sure tests don't fail on slower systems or during a process pause
|
||||
now := time.Now()
|
||||
testCases := []struct {
|
||||
ts time.Time
|
||||
testID string
|
||||
result time.Time
|
||||
err error
|
||||
periods []Period
|
||||
}{
|
||||
{
|
||||
testID: "No choices",
|
||||
ts: now,
|
||||
result: time.Time{},
|
||||
err: ErrNoNextChangeover,
|
||||
periods: []Period{},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is second",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one is a year, other a minute",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
testID: "Two Choices, shorter is first",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one in the past",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one invalid",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, Identical periods",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "One choice",
|
||||
ts: now,
|
||||
result: now.Add(time.Minute),
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testID, func(t *testing.T) {
|
||||
ts, err := GetNextChangeOver(now, tc.periods...)
|
||||
if tc.err != err {
|
||||
t.Errorf("Error %v does not match expected %v", tc.err, err)
|
||||
}
|
||||
if ts != tc.result {
|
||||
t.Errorf("Got %v but expected %v", ts, tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEndAfterStart occurs when a period given has an end time after its start time
|
||||
// ErrEndAfterStart occurs when a period's start time is after its end time
|
||||
ErrEndAfterStart = errors.New("error: start time is after end time")
|
||||
// ErrNoValidPeriods occurs when an empty set of periods is passed or when ll periods are invalid
|
||||
// ErrNoValidPeriods occurs when an empty set of periods is passed or when all periods are invalid
|
||||
ErrNoValidPeriods = errors.New("error: no valid periods available")
|
||||
// ErrNoNextChangeover occurs when GetNextChangeover is called but there are no changeovers after t
|
||||
ErrNoNextChangeover = errors.New("error: no valid changeovers available")
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ func MostSpecificPeriod(ts time.Time, periods ...Period) (id string, err error)
|
||||
return "", ErrNoValidPeriods
|
||||
}
|
||||
// find the shortest duration
|
||||
d, err := GetDuration(periods[0].GetStartTime(), periods[0].GetEndTime())
|
||||
d, _ := GetDuration(periods[0].GetStartTime(), periods[0].GetEndTime())
|
||||
for _, x := range periods {
|
||||
p, err := GetDuration(x.GetStartTime(), x.GetEndTime())
|
||||
if err == nil && p < d {
|
||||
@@ -34,7 +34,7 @@ func MostSpecificPeriod(ts time.Time, periods ...Period) (id string, err error)
|
||||
newest = x.GetStartTime()
|
||||
}
|
||||
}
|
||||
// Determine whichever of these periods have the same start time in addtion to duration
|
||||
// Determine whichever of these periods have the same start time in addition to duration
|
||||
var matchingDurationsAndStartTimes []Period
|
||||
for _, x := range matchingDurations {
|
||||
if x.GetStartTime() == newest {
|
||||
@@ -61,7 +61,9 @@ func GetDuration(start time.Time, end time.Time) (dur time.Duration, err error)
|
||||
func ValidTimePeriods(ts time.Time, periods ...Period) []Period {
|
||||
var valid []Period
|
||||
for _, p := range periods {
|
||||
if p.GetStartTime().Before(ts) && p.GetEndTime().After(ts) {
|
||||
start := p.GetStartTime()
|
||||
end := p.GetEndTime()
|
||||
if (start.Before(ts) || start.Equal(ts)) && (end.After(ts)) {
|
||||
valid = append(valid, p)
|
||||
}
|
||||
}
|
||||
|
||||
166
msp/msp_test.go
166
msp/msp_test.go
@@ -1,28 +1,11 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimeWindow struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Identifier string
|
||||
}
|
||||
|
||||
func (p TimeWindow) GetIdentifier() string {
|
||||
return p.Identifier
|
||||
}
|
||||
func (p TimeWindow) GetEndTime() time.Time {
|
||||
return p.EndTime
|
||||
}
|
||||
func (p TimeWindow) GetStartTime() time.Time {
|
||||
return p.StartTime
|
||||
}
|
||||
|
||||
//(periods ...Period) (id string, err error) {
|
||||
// (periods ...Period) (id string, err error) {
|
||||
func TestMostSpecificPeriod(t *testing.T) {
|
||||
// use a static timestamp to make sure tests don't fail on slower systems or during a process pause
|
||||
now := time.Now()
|
||||
@@ -32,81 +15,137 @@ func TestMostSpecificPeriod(t *testing.T) {
|
||||
result string
|
||||
err error
|
||||
periods []Period
|
||||
}{{testID: "No choices",
|
||||
ts: now,
|
||||
result: "",
|
||||
err: ErrNoValidPeriods,
|
||||
periods: []Period{}},
|
||||
{testID: "Two Choices, shorter is second",
|
||||
}{
|
||||
{
|
||||
testID: "No choices",
|
||||
ts: now,
|
||||
result: "",
|
||||
err: ErrNoValidPeriods,
|
||||
periods: []Period{},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is second",
|
||||
ts: now,
|
||||
result: "B",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-2 * time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B"}}},
|
||||
{testID: "Two Choices, one is a year, other a minute",
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one is a year, other a minute",
|
||||
ts: now,
|
||||
result: "B",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-5 * time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B"}}},
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{testID: "Two Choices, shorter is first",
|
||||
{
|
||||
testID: "Two Choices, shorter is first",
|
||||
ts: now,
|
||||
result: "A",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-5 * time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B"}}},
|
||||
{testID: "Two Choices, one in the past",
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one in the past",
|
||||
ts: now,
|
||||
result: "A",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-2 * time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "B"}}},
|
||||
{testID: "Two Choices, one invalid",
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one invalid",
|
||||
ts: now,
|
||||
result: "B",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-2 * time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B"}}},
|
||||
{testID: "Two Choices, Identical periods",
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, Identical periods",
|
||||
ts: now,
|
||||
result: "B",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"},
|
||||
TimeWindow{StartTime: now.Add(-time.Minute),
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B"}}},
|
||||
{testID: "One choice",
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "One choice",
|
||||
ts: now,
|
||||
result: "A",
|
||||
err: nil,
|
||||
periods: []Period{TimeWindow{StartTime: now.Add(-time.Minute),
|
||||
periods: []Period{TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A"}}}}
|
||||
Identifier: "A",
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s", tc.testID), func(t *testing.T) {
|
||||
t.Run(tc.testID, func(t *testing.T) {
|
||||
id, err := MostSpecificPeriod(tc.ts, tc.periods...)
|
||||
if id != tc.result {
|
||||
t.Errorf("ID '%s' does not match expected '%s'", id, tc.result)
|
||||
@@ -114,7 +153,6 @@ func TestMostSpecificPeriod(t *testing.T) {
|
||||
if err != tc.err {
|
||||
t.Errorf("Error '%v' does not match expected '%v'", err, tc.err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
57
msp/timeline.go
Normal file
57
msp/timeline.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimeWindow struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Identifier string
|
||||
}
|
||||
|
||||
func (p TimeWindow) GetIdentifier() string {
|
||||
return p.Identifier
|
||||
}
|
||||
|
||||
func (p TimeWindow) GetEndTime() time.Time {
|
||||
return p.EndTime
|
||||
}
|
||||
|
||||
func (p TimeWindow) GetStartTime() time.Time {
|
||||
return p.StartTime
|
||||
}
|
||||
|
||||
func (t TimeWindow) String() string {
|
||||
return fmt.Sprintf("%s\t%s\t%s",
|
||||
t.GetIdentifier(),
|
||||
t.GetStartTime(),
|
||||
t.GetEndTime())
|
||||
}
|
||||
|
||||
// Outputs a formatted timeline of periods
|
||||
func GenerateTimeline(periods ...Period) (out []Period) {
|
||||
if len(periods) == 0 {
|
||||
return out
|
||||
}
|
||||
periodsByID := make(map[string]Period)
|
||||
ids := FlattenPeriods(periods...)
|
||||
for _, val := range periods {
|
||||
id := val.GetIdentifier()
|
||||
periodsByID[id] = val
|
||||
}
|
||||
start := periodsByID[ids[0]].GetStartTime()
|
||||
for _, val := range ids {
|
||||
next, err := GetNextChangeOver(start, periods...)
|
||||
if err == nil {
|
||||
if next.Equal(periodsByID[val].GetStartTime()) {
|
||||
start = periodsByID[val].GetStartTime()
|
||||
next = periodsByID[val].GetEndTime()
|
||||
}
|
||||
out = append(out, TimeWindow{StartTime: start, EndTime: next, Identifier: val})
|
||||
start = next
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
278
msp/timeline_test.go
Normal file
278
msp/timeline_test.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// (periods ...Period) (id string, err error) {
|
||||
func TestGenerateTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
testCases := []struct {
|
||||
ts time.Time
|
||||
testID string
|
||||
result []string
|
||||
periods []Period
|
||||
}{
|
||||
{
|
||||
testID: "No choices",
|
||||
ts: now,
|
||||
result: []string{},
|
||||
periods: []Period{},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is second",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-5*time.Minute), now.Add(-2*time.Minute)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-2*time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one is a year, other a minute",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-1*time.Hour*24*365), now.Add(-5*time.Minute)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-5*time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-1 * time.Hour * 24 * 365),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, shorter is first",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-5*time.Minute), now.Add(-2*time.Minute)),
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-2*time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-5 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one in the past",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-2*time.Minute), now.Add(-time.Minute)),
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, one invalid",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-2*time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute),
|
||||
EndTime: now.Add(-time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-2 * time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "Two Choices, Identical periods",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(-time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "One choice",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Minute), now.Add(time.Minute)),
|
||||
},
|
||||
periods: []Period{TimeWindow{
|
||||
StartTime: now.Add(-time.Minute),
|
||||
EndTime: now.Add(time.Minute),
|
||||
Identifier: "A",
|
||||
}},
|
||||
},
|
||||
{
|
||||
testID: "not in current point in time",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Hour*24*30), now.Add(-time.Hour*24*29)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(time.Hour*24*90), now.Add(time.Hour*24*120)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 30),
|
||||
EndTime: now.Add(-time.Hour * 24 * 29),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Hour * 24 * 90),
|
||||
EndTime: now.Add(time.Hour * 24 * 120),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "three overlapping periods",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(-time.Hour*24*31), now.Add(-time.Hour*24*30)),
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Hour*24*30), now.Add(-time.Hour*24*29)),
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(-time.Hour*24*29), now.Add(time.Hour*24*90)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(time.Hour*24*90), now.Add(time.Hour*24*120)),
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(time.Hour*24*120), now.Add(time.Hour*24*140)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 30),
|
||||
EndTime: now.Add(-time.Hour * 24 * 29),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Hour * 24 * 90),
|
||||
EndTime: now.Add(time.Hour * 24 * 120),
|
||||
Identifier: "B",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 31),
|
||||
EndTime: now.Add(time.Hour * 24 * 140),
|
||||
Identifier: "C",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "multiple overlapping periods",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("D\t%s\t%s\n", now.Add(-time.Hour*24*150), now.Add(-time.Hour*24*65)),
|
||||
fmt.Sprintf("E\t%s\t%s\n", now.Add(-time.Hour*24*65), now.Add(-time.Hour*24*31)),
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(-time.Hour*24*31), now.Add(-time.Hour*24*30)),
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Hour*24*30), now.Add(-time.Hour*24*29)),
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(-time.Hour*24*29), now.Add(time.Hour*24*90)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(time.Hour*24*90), now.Add(time.Hour*24*120)),
|
||||
fmt.Sprintf("C\t%s\t%s\n", now.Add(time.Hour*24*120), now.Add(time.Hour*24*140)),
|
||||
fmt.Sprintf("E\t%s\t%s\n", now.Add(time.Hour*24*140), now.Add(time.Hour*24*175)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 30),
|
||||
EndTime: now.Add(-time.Hour * 24 * 29),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Hour * 24 * 90),
|
||||
EndTime: now.Add(time.Hour * 24 * 120),
|
||||
Identifier: "B",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 31),
|
||||
EndTime: now.Add(time.Hour * 24 * 140),
|
||||
Identifier: "C",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 150),
|
||||
EndTime: now.Add(time.Hour * 24 * 175),
|
||||
Identifier: "D",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Hour * 24 * 65),
|
||||
EndTime: now.Add(time.Hour * 24 * 175),
|
||||
Identifier: "E",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testID: "periods with a gap in the middle",
|
||||
ts: now,
|
||||
result: []string{
|
||||
fmt.Sprintf("A\t%s\t%s\n", now.Add(-time.Minute*10), now.Add(-time.Minute*5)),
|
||||
fmt.Sprintf("B\t%s\t%s\n", now.Add(time.Minute*5), now.Add(time.Minute*10)),
|
||||
},
|
||||
periods: []Period{
|
||||
TimeWindow{
|
||||
StartTime: now.Add(-time.Minute * 10),
|
||||
EndTime: now.Add(-time.Minute * 5),
|
||||
Identifier: "A",
|
||||
},
|
||||
TimeWindow{
|
||||
StartTime: now.Add(time.Minute * 5),
|
||||
EndTime: now.Add(time.Minute * 10),
|
||||
Identifier: "B",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testID, func(t *testing.T) {
|
||||
timeline := GenerateTimeline(tc.periods...)
|
||||
if len(timeline) != len(tc.result) {
|
||||
t.Fatalf("Time line had %d results, expected %d", len(timeline), len(tc.result))
|
||||
}
|
||||
for idx, period := range timeline {
|
||||
if period.(TimeWindow).String()+"\n" != tc.result[idx] {
|
||||
t.Errorf("Expected:\t%s\nHad:\t%s", period, tc.result[idx])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package msp
|
||||
|
||||
import "time"
|
||||
|
||||
// Compile-time interface check.
|
||||
var _ Period = TimeWindow{}
|
||||
|
||||
type Period interface {
|
||||
GetStartTime() time.Time
|
||||
GetEndTime() time.Time
|
||||
|
||||
Reference in New Issue
Block a user