-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmetrics.go
188 lines (160 loc) · 6.41 KB
/
metrics.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package rulesengine
import (
"time"
)
type MetricPeriod string
const (
MetricPeriodAllTime MetricPeriod = "all_time"
MetricPeriodCurrentDay MetricPeriod = "current_day"
MetricPeriodCurrentMonth MetricPeriod = "current_month"
MetricPeriodCurrentWeek MetricPeriod = "current_week"
)
// For MetricPeriodMonth, there's an additional option indicating when the month should reset
type MetricPeriodMonthReset string
const (
MetricPeriodMonthResetFirst MetricPeriodMonthReset = "first_of_month"
MetricPeriodMonthResetBilling MetricPeriodMonthReset = "billing_cycle"
)
// Given a calendar-based metric period, return the beginning of the current metric period
// Will return nil for non-calendar-based metric periods such as all-time or billing cycle
func GetCurrentMetricPeriodStartForCalendarMetricPeriod(metricPeriod MetricPeriod) *time.Time {
switch metricPeriod {
case MetricPeriodCurrentDay:
// UTC midnight for the current day
today := time.Now().UTC().Truncate(24 * time.Hour)
return &today
case MetricPeriodCurrentWeek:
// UTC midnight for the most recent Sunday
now := time.Now().UTC()
daysSinceSunday := int(now.Weekday())
currentSunday := now.Truncate(24 * time.Hour).Add(-time.Duration(daysSinceSunday) * 24 * time.Hour)
return ¤tSunday
case MetricPeriodCurrentMonth:
// UTC midnight for the first day of current month
now := time.Now().UTC()
firstDayOfCurrentMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
return &firstDayOfCurrentMonth
}
return nil
}
// Given a company, determine the beginning of the current metric period based on the company's billing subscription
func GetCurrentMetricPeriodStartForCompanyBillingSubscription(company *Company) *time.Time {
// if no subscription exists, we use calendar month reset
if company == nil || company.Subscription == nil {
return GetCurrentMetricPeriodStartForCalendarMetricPeriod(MetricPeriodCurrentMonth)
}
now := time.Now().UTC()
periodStart := company.Subscription.PeriodStart
// if the start period is in the future, the metric period is from the start of the current calendar month
if periodStart.After(now) {
return GetCurrentMetricPeriodStartForCalendarMetricPeriod(MetricPeriodCurrentMonth)
}
// find the most recent reset date based on subscription start date
currentReset := time.Date(
now.Year(),
now.Month(),
periodStart.Day(),
periodStart.Hour(),
periodStart.Minute(),
periodStart.Second(),
periodStart.Nanosecond(),
time.UTC,
)
// if the reset date for current month is in the future, use previous month's reset date
if currentReset.After(now) {
currentReset = currentReset.AddDate(0, -1, 0)
}
// if the current reset is before the subscription period start, use the period start instead
if currentReset.Before(periodStart) {
return &periodStart
}
return ¤tReset
}
// Given a calendar-based metric period, return the next metric period reset time
// Will return nil for non-calendar-based metric periods such as all-time or billing cycle
func GetNextMetricPeriodStartForCalendarMetricPeriod(metricPeriod MetricPeriod) *time.Time {
switch metricPeriod {
case MetricPeriodCurrentDay:
// UTC midnight for upcoming day
tomorrow := time.Now().UTC().Truncate(24 * time.Hour).Add(24 * time.Hour)
return &tomorrow
case MetricPeriodCurrentWeek:
// UTC midnight for upcoming Sunday
now := time.Now().UTC()
daysUntilSunday := (7 - int(now.Weekday())) % 7
if daysUntilSunday == 0 {
// if it is currently sunday, we want to look forward to the next sunday
daysUntilSunday = 7
}
upcomingSunday := now.Truncate(24 * time.Hour).Add(time.Duration(daysUntilSunday) * 24 * time.Hour)
return &upcomingSunday
case MetricPeriodCurrentMonth:
// UTC midnight for the first day of next month
now := time.Now().UTC()
firstDayOfCurrentMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
nextMonth := firstDayOfCurrentMonth.AddDate(0, 1, 0)
return &nextMonth
}
return nil
}
// Given a company, determine the next metric period start based on the company's billing subscription
func GetNextMetricPeriodStartForCompanyBillingSubscription(company *Company) *time.Time {
// if no subscription exists, we use calendar month reset
if company == nil || company.Subscription == nil {
return GetNextMetricPeriodStartForCalendarMetricPeriod(MetricPeriodCurrentMonth)
}
now := time.Now().UTC()
periodEnd := company.Subscription.PeriodEnd
periodStart := company.Subscription.PeriodStart
// if the start period is in the future, the metric period is from the start of the current calendar month until either
// the end of the current calendar month or the start of the billing period, whichever comes first
if periodStart.After(now) {
startOfNextMonth := GetNextMetricPeriodStartForCalendarMetricPeriod(MetricPeriodCurrentMonth)
if periodStart.After(*startOfNextMonth) {
return startOfNextMonth
}
return &periodStart
}
// month metric period will reset on the same day/hour/minute/second as the susbcription started every month; get that timestamp for the current month
nextReset := time.Date(
now.Year(),
now.Month(),
periodStart.Day(),
periodStart.Hour(),
periodStart.Minute(),
periodStart.Second(),
periodStart.Nanosecond(),
time.UTC,
)
// if we've already passed this month's reset date, move to next month
if !nextReset.After(now) {
nextReset = nextReset.AddDate(0, 1, 0)
}
// if the next reset is after the end of the billing period, use the end of the billing period instead
if nextReset.After(periodEnd) {
return &periodEnd
}
return &nextReset
}
// Given a rule condition and a company, determine the next metric period start
// Will return nil if the condition is not a metric condition
func GetNextMetricPeriodStartFromCondition(
condition *Condition,
company *Company,
) *time.Time {
// Only metric conditions have a metric period that can reset
if condition == nil || condition.ConditionType != ConditionTypeMetric {
return nil
}
// If the metric period is all-time, no reset
if condition.MetricPeriod == nil {
return nil
}
// Metric period current month with billing cycle reset
monthReset := condition.MetricPeriodMonthReset
if *condition.MetricPeriod == MetricPeriodCurrentMonth && monthReset != nil && *monthReset == MetricPeriodMonthResetBilling {
return GetNextMetricPeriodStartForCompanyBillingSubscription(company)
}
// Calendar-based metric periods
return GetNextMetricPeriodStartForCalendarMetricPeriod(*condition.MetricPeriod)
}