Skip to content

Commit dd90dae

Browse files
committed
Merge remote-tracking branch 'origin/feat/finops-open-api-v1' into kubecon-2025
# Conflicts: # common-lib/utils/TimeUtils.go
2 parents 932f970 + e776d42 commit dd90dae

4 files changed

Lines changed: 907 additions & 5 deletions

File tree

common-lib/utils/TimeUtils.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package utils
1818

1919
import (
2020
"fmt"
21+
"strings"
2122
"time"
2223
)
2324

@@ -56,6 +57,7 @@ const (
5657
Quarter TimeWindows = "quarter"
5758
LastWeek TimeWindows = "lastWeek"
5859
LastMonth TimeWindows = "lastMonth"
60+
Year TimeWindows = "year"
5961
LastQuarter TimeWindows = "lastQuarter"
6062
)
6163

@@ -135,6 +137,9 @@ func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeReques
135137
prevQuarterEnd := currentQuarterStart.Add(-time.Second)
136138

137139
return NewTimeRangeRequest(&prevQuarterStart, &prevQuarterEnd), nil
140+
case Year:
141+
start := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
142+
return NewTimeRangeRequest(&start, &now), nil
138143
default:
139144
return NewTimeRangeRequest(&time.Time{}, &time.Time{}), fmt.Errorf("unsupported time window: %q", *timeRange.TimeWindow)
140145
}
@@ -150,3 +155,169 @@ func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeReques
150155
return NewTimeRangeRequest(&time.Time{}, &time.Time{}), fmt.Errorf("from and to dates are required if time window is not provided")
151156
}
152157
}
158+
159+
// TimeBoundariesRequest represents the request for time boundary frames
160+
type TimeBoundariesRequest struct {
161+
TimeWindowBoundaries []string `json:"timeWindowBoundaries" schema:"timeWindowBoundaries" validate:"omitempty, min=1"`
162+
TimeWindow *TimeWindows `json:"timeWindow" schema:"timeWindow" validate:"omitempty, oneof=week month quarter year"` // week, month, quarter, year
163+
Iterations int `json:"iterations" schema:"iterations" validate:"omitempty, min=1"`
164+
}
165+
166+
// TimeWindowBoundaries represents the start and end times for a time window
167+
type TimeWindowBoundaries struct {
168+
StartTime time.Time
169+
EndTime time.Time
170+
}
171+
172+
func (timeBoundaries *TimeBoundariesRequest) ParseAndValidateTimeBoundaries() ([]TimeWindowBoundaries, error) {
173+
if timeBoundaries == nil {
174+
return []TimeWindowBoundaries{}, fmt.Errorf("invalid time boundaries request")
175+
}
176+
// If timeWindow is provided, it takes preference over timeWindowBoundaries
177+
if timeBoundaries.TimeWindow != nil {
178+
switch *timeBoundaries.TimeWindow {
179+
case Week:
180+
return GetWeeklyTimeBoundaries(timeBoundaries.Iterations), nil
181+
case Month:
182+
return GetMonthlyTimeBoundaries(timeBoundaries.Iterations), nil
183+
case Quarter:
184+
return GetQuarterlyTimeBoundaries(timeBoundaries.Iterations), nil
185+
case Year:
186+
return GetYearlyTimeBoundaries(timeBoundaries.Iterations), nil
187+
default:
188+
return []TimeWindowBoundaries{}, fmt.Errorf("unsupported time window: %q", *timeBoundaries.TimeWindow)
189+
}
190+
} else if len(timeBoundaries.TimeWindowBoundaries) != 0 {
191+
// Validate time window
192+
return DecodeAndValidateTimeWindowBoundaries(timeBoundaries.TimeWindowBoundaries)
193+
} else {
194+
return []TimeWindowBoundaries{}, fmt.Errorf("time window boundaries are required if time window is not provided")
195+
}
196+
}
197+
198+
func GetWeeklyTimeBoundaries(iterations int) []TimeWindowBoundaries {
199+
if iterations <= 0 {
200+
return []TimeWindowBoundaries{}
201+
}
202+
boundaries := make([]TimeWindowBoundaries, iterations)
203+
now := time.Now()
204+
weekday := int(now.Weekday())
205+
if weekday == 0 {
206+
weekday = 7
207+
}
208+
// Get start of this week (Monday)
209+
weekStart := now.AddDate(0, 0, -(weekday - 1))
210+
// Set time to midnight
211+
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, weekStart.Location())
212+
213+
for i := 0; i < iterations; i++ {
214+
start := weekStart.AddDate(0, 0, -7*i)
215+
end := start.AddDate(0, 0, 7)
216+
// For the current week, if now < end, set end = now
217+
if i == 0 && now.Before(end) {
218+
end = now
219+
}
220+
boundaries[i] = TimeWindowBoundaries{
221+
StartTime: start,
222+
EndTime: end,
223+
}
224+
}
225+
return boundaries
226+
}
227+
228+
func GetMonthlyTimeBoundaries(iterations int) []TimeWindowBoundaries {
229+
if iterations <= 0 {
230+
return []TimeWindowBoundaries{}
231+
}
232+
boundaries := make([]TimeWindowBoundaries, iterations)
233+
now := time.Now()
234+
// Get start of this month (1st)
235+
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
236+
for i := 0; i < iterations; i++ {
237+
start := monthStart.AddDate(0, -i, 0)
238+
end := start.AddDate(0, 1, 0)
239+
// For the current month, if now < end, set end = now
240+
if i == 0 && now.Before(end) {
241+
end = now
242+
}
243+
boundaries[i] = TimeWindowBoundaries{
244+
StartTime: start,
245+
EndTime: end,
246+
}
247+
}
248+
return boundaries
249+
}
250+
251+
func GetQuarterlyTimeBoundaries(iterations int) []TimeWindowBoundaries {
252+
if iterations <= 0 {
253+
return []TimeWindowBoundaries{}
254+
}
255+
boundaries := make([]TimeWindowBoundaries, iterations)
256+
now := time.Now()
257+
quarter := ((int(now.Month()) - 1) / 3) + 1
258+
quarterMonth := time.Month((quarter-1)*3 + 1)
259+
// Get start of this quarter (1st of the month)
260+
quarterStart := time.Date(now.Year(), quarterMonth, 1, 0, 0, 0, 0, now.Location())
261+
for i := 0; i < iterations; i++ {
262+
start := quarterStart.AddDate(0, -3*i, 0)
263+
end := start.AddDate(0, 3, 0)
264+
// For the current quarter, if now < end, set end = now
265+
if i == 0 && now.Before(end) {
266+
end = now
267+
}
268+
boundaries[i] = TimeWindowBoundaries{
269+
StartTime: start,
270+
EndTime: end,
271+
}
272+
}
273+
return boundaries
274+
}
275+
276+
func GetYearlyTimeBoundaries(iterations int) []TimeWindowBoundaries {
277+
if iterations <= 0 {
278+
return []TimeWindowBoundaries{}
279+
}
280+
boundaries := make([]TimeWindowBoundaries, iterations)
281+
now := time.Now()
282+
// Get start of this year (1st of January)
283+
yearStart := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
284+
for i := 0; i < iterations; i++ {
285+
start := yearStart.AddDate(-i, 0, 0)
286+
end := start.AddDate(1, 0, 0)
287+
// For the current year, if now < end, set end = now
288+
if i == 0 && now.Before(end) {
289+
end = now
290+
}
291+
boundaries[i] = TimeWindowBoundaries{
292+
StartTime: start,
293+
EndTime: end,
294+
}
295+
}
296+
return boundaries
297+
}
298+
299+
func DecodeAndValidateTimeWindowBoundaries(timeWindowBoundaries []string) ([]TimeWindowBoundaries, error) {
300+
boundaries := make([]TimeWindowBoundaries, 0, len(timeWindowBoundaries))
301+
for _, boundary := range timeWindowBoundaries {
302+
parts := strings.Split(boundary, "|")
303+
if len(parts) != 2 {
304+
return nil, fmt.Errorf("invalid time window boundary format: %q", boundary)
305+
}
306+
startTime, err := time.Parse(time.RFC3339, parts[0])
307+
if err != nil {
308+
return nil, fmt.Errorf("invalid start time format: %q. expected format: %q", parts[0], time.RFC3339)
309+
}
310+
endTime, err := time.Parse(time.RFC3339, parts[1])
311+
if err != nil {
312+
return nil, fmt.Errorf("invalid end time format: %q. expected format: %q", parts[1], time.RFC3339)
313+
}
314+
if startTime.After(endTime) {
315+
return nil, fmt.Errorf("start time cannot be after end time: %q", boundary)
316+
}
317+
boundaries = append(boundaries, TimeWindowBoundaries{
318+
StartTime: startTime,
319+
EndTime: endTime,
320+
})
321+
}
322+
return boundaries, nil
323+
}

0 commit comments

Comments
 (0)