-
Notifications
You must be signed in to change notification settings - Fork 153
/
window.go
129 lines (109 loc) · 3.4 KB
/
window.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package execute
import (
"time"
"github.com/influxdata/flux/codes"
"github.com/influxdata/flux/internal/errors"
"github.com/influxdata/flux/values"
)
type Window struct {
Every Duration
Period Duration
Offset Duration
}
// NewWindow creates a window with the given parameters,
// and normalizes the offset to a small positive duration.
// It also validates that the durations are valid when
// used within a window.
func NewWindow(every, period, offset Duration) (Window, error) {
// Normalize the offset to a small positive duration
offset = offset.Normalize(every)
w := Window{
Every: every,
Period: period,
Offset: offset,
}
if err := w.IsValid(); err != nil {
return Window{}, err
}
return w, nil
}
type truncateFunc func(t Time, d Duration) Time
func (w *Window) getTruncateFunc(d Duration) (truncateFunc, error) {
switch months, nsecs := d.Months(), d.Nanoseconds(); {
case months != 0 && nsecs != 0:
return nil, errors.New(codes.Invalid, "duration used as an interval cannot mix month and nanosecond units")
case months != 0:
return truncateByMonths, nil
case nsecs != 0:
return truncateByNsecs, nil
default:
return nil, errors.New(codes.Invalid, "duration used as an interval cannot be zero")
}
}
// truncate will truncate the time using the duration.
func (w *Window) truncate(t Time) Time {
fn, err := w.getTruncateFunc(w.Every)
if err != nil {
panic(err)
}
return fn(t, w.Every)
}
// IsValid will check if this Window is valid and it will
// return an error if it isn't.
func (w Window) IsValid() error {
_, err := w.getTruncateFunc(w.Every)
return err
}
// GetEarliestBounds returns the bounds for the earliest window bounds
// that contains the given time t. For underlapping windows that
// do not contain time t, the window directly after time t will be returned.
func (w Window) GetEarliestBounds(t Time) Bounds {
// translate to not-offset coordinate
t = t.Add(w.Offset.Mul(-1))
stop := w.truncate(t).Add(w.Every)
// translate to offset coordinate
stop = stop.Add(w.Offset)
start := stop.Add(w.Period.Mul(-1))
return Bounds{
Start: start,
Stop: stop,
}
}
// GetOverlappingBounds returns a slice of bounds for each window
// that overlaps the input bounds b.
func (w Window) GetOverlappingBounds(b Bounds) []Bounds {
if b.IsEmpty() {
return []Bounds{}
}
// Estimate the number of windows by using a rough approximation.
c := (b.Duration().Duration() / w.Every.Duration()) + (w.Period.Duration() / w.Every.Duration())
bs := make([]Bounds, 0, c)
bi := w.GetEarliestBounds(b.Start)
for bi.Start < b.Stop {
bs = append(bs, bi)
bi.Start = bi.Start.Add(w.Every)
bi.Stop = bi.Stop.Add(w.Every)
}
return bs
}
// truncateByNsecs will truncate the time to the given number
// of nanoseconds.
func truncateByNsecs(t Time, d Duration) Time {
remainder := int64(t) % d.Nanoseconds()
return t - Time(remainder)
}
// truncateByMonths will truncate the time to the given
// number of months.
func truncateByMonths(t Time, d Duration) Time {
ts := t.Time()
year, month, _ := ts.Date()
// Determine the total number of months and truncate
// the number of months by the duration amount.
total := int64(year*12) + int64(month-1)
remainder := total % d.Months()
total -= remainder
// Recreate a new time from the year and month combination.
year, month = int(total/12), time.Month(total%12)+1
ts = time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
return values.ConvertTime(ts)
}