@@ -18,6 +18,7 @@ package utils
1818
1919import (
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