@@ -2,6 +2,7 @@ package utils
22
33import (
44 "fmt"
5+ "strings"
56 "time"
67)
78
@@ -40,6 +41,7 @@ const (
4041 Quarter TimeWindows = "quarter"
4142 LastWeek TimeWindows = "lastWeek"
4243 LastMonth TimeWindows = "lastMonth"
44+ Year TimeWindows = "year"
4345)
4446
4547func (timeRange * TimeRangeRequest ) ParseAndValidateTimeRange () (* TimeRangeRequest , error ) {
@@ -87,6 +89,9 @@ func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeReques
8789 lastMonthStart := thisMonthStart .AddDate (0 , - 1 , 0 )
8890 lastMonthEnd := thisMonthStart .Add (- time .Second )
8991 return NewTimeRangeRequest (& lastMonthStart , & lastMonthEnd ), nil
92+ case Year :
93+ start := time .Date (now .Year (), 1 , 1 , 0 , 0 , 0 , 0 , now .Location ())
94+ return NewTimeRangeRequest (& start , & now ), nil
9095 default :
9196 return NewTimeRangeRequest (& time.Time {}, & time.Time {}), fmt .Errorf ("unsupported time window: %q" , * timeRange .TimeWindow )
9297 }
@@ -102,3 +107,169 @@ func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeReques
102107 return NewTimeRangeRequest (& time.Time {}, & time.Time {}), fmt .Errorf ("from and to dates are required if time window is not provided" )
103108 }
104109}
110+
111+ // TimeBoundariesRequest represents the request for time boundary frames
112+ type TimeBoundariesRequest struct {
113+ TimeWindowBoundaries []string `json:"timeWindowBoundaries" schema:"timeWindowBoundaries" validate:"omitempty, min=1"`
114+ TimeWindow * TimeWindows `json:"timeWindow" schema:"timeWindow" validate:"omitempty, oneof=week month quarter year"` // week, month, quarter, year
115+ Iterations int `json:"iterations" schema:"iterations" validate:"omitempty, min=1"`
116+ }
117+
118+ // TimeWindowBoundaries represents the start and end times for a time window
119+ type TimeWindowBoundaries struct {
120+ StartTime time.Time
121+ EndTime time.Time
122+ }
123+
124+ func (timeBoundaries * TimeBoundariesRequest ) ParseAndValidateTimeBoundaries () ([]TimeWindowBoundaries , error ) {
125+ if timeBoundaries == nil {
126+ return []TimeWindowBoundaries {}, fmt .Errorf ("invalid time boundaries request" )
127+ }
128+ // If timeWindow is provided, it takes preference over timeWindowBoundaries
129+ if timeBoundaries .TimeWindow != nil {
130+ switch * timeBoundaries .TimeWindow {
131+ case Week :
132+ return GetWeeklyTimeBoundaries (timeBoundaries .Iterations ), nil
133+ case Month :
134+ return GetMonthlyTimeBoundaries (timeBoundaries .Iterations ), nil
135+ case Quarter :
136+ return GetQuarterlyTimeBoundaries (timeBoundaries .Iterations ), nil
137+ case Year :
138+ return GetYearlyTimeBoundaries (timeBoundaries .Iterations ), nil
139+ default :
140+ return []TimeWindowBoundaries {}, fmt .Errorf ("unsupported time window: %q" , * timeBoundaries .TimeWindow )
141+ }
142+ } else if len (timeBoundaries .TimeWindowBoundaries ) != 0 {
143+ // Validate time window
144+ return DecodeAndValidateTimeWindowBoundaries (timeBoundaries .TimeWindowBoundaries )
145+ } else {
146+ return []TimeWindowBoundaries {}, fmt .Errorf ("time window boundaries are required if time window is not provided" )
147+ }
148+ }
149+
150+ func GetWeeklyTimeBoundaries (iterations int ) []TimeWindowBoundaries {
151+ if iterations <= 0 {
152+ return []TimeWindowBoundaries {}
153+ }
154+ boundaries := make ([]TimeWindowBoundaries , iterations )
155+ now := time .Now ()
156+ weekday := int (now .Weekday ())
157+ if weekday == 0 {
158+ weekday = 7
159+ }
160+ // Get start of this week (Monday)
161+ weekStart := now .AddDate (0 , 0 , - (weekday - 1 ))
162+ // Set time to midnight
163+ weekStart = time .Date (weekStart .Year (), weekStart .Month (), weekStart .Day (), 0 , 0 , 0 , 0 , weekStart .Location ())
164+
165+ for i := 0 ; i < iterations ; i ++ {
166+ start := weekStart .AddDate (0 , 0 , - 7 * i )
167+ end := start .AddDate (0 , 0 , 7 )
168+ // For the current week, if now < end, set end = now
169+ if i == 0 && now .Before (end ) {
170+ end = now
171+ }
172+ boundaries [i ] = TimeWindowBoundaries {
173+ StartTime : start ,
174+ EndTime : end ,
175+ }
176+ }
177+ return boundaries
178+ }
179+
180+ func GetMonthlyTimeBoundaries (iterations int ) []TimeWindowBoundaries {
181+ if iterations <= 0 {
182+ return []TimeWindowBoundaries {}
183+ }
184+ boundaries := make ([]TimeWindowBoundaries , iterations )
185+ now := time .Now ()
186+ // Get start of this month (1st)
187+ monthStart := time .Date (now .Year (), now .Month (), 1 , 0 , 0 , 0 , 0 , now .Location ())
188+ for i := 0 ; i < iterations ; i ++ {
189+ start := monthStart .AddDate (0 , - i , 0 )
190+ end := start .AddDate (0 , 1 , 0 )
191+ // For the current month, if now < end, set end = now
192+ if i == 0 && now .Before (end ) {
193+ end = now
194+ }
195+ boundaries [i ] = TimeWindowBoundaries {
196+ StartTime : start ,
197+ EndTime : end ,
198+ }
199+ }
200+ return boundaries
201+ }
202+
203+ func GetQuarterlyTimeBoundaries (iterations int ) []TimeWindowBoundaries {
204+ if iterations <= 0 {
205+ return []TimeWindowBoundaries {}
206+ }
207+ boundaries := make ([]TimeWindowBoundaries , iterations )
208+ now := time .Now ()
209+ quarter := ((int (now .Month ()) - 1 ) / 3 ) + 1
210+ quarterMonth := time .Month ((quarter - 1 )* 3 + 1 )
211+ // Get start of this quarter (1st of the month)
212+ quarterStart := time .Date (now .Year (), quarterMonth , 1 , 0 , 0 , 0 , 0 , now .Location ())
213+ for i := 0 ; i < iterations ; i ++ {
214+ start := quarterStart .AddDate (0 , - 3 * i , 0 )
215+ end := start .AddDate (0 , 3 , 0 )
216+ // For the current quarter, 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 GetYearlyTimeBoundaries (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 year (1st of January)
235+ yearStart := time .Date (now .Year (), 1 , 1 , 0 , 0 , 0 , 0 , now .Location ())
236+ for i := 0 ; i < iterations ; i ++ {
237+ start := yearStart .AddDate (- i , 0 , 0 )
238+ end := start .AddDate (1 , 0 , 0 )
239+ // For the current year, 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 DecodeAndValidateTimeWindowBoundaries (timeWindowBoundaries []string ) ([]TimeWindowBoundaries , error ) {
252+ boundaries := make ([]TimeWindowBoundaries , 0 , len (timeWindowBoundaries ))
253+ for _ , boundary := range timeWindowBoundaries {
254+ parts := strings .Split (boundary , "|" )
255+ if len (parts ) != 2 {
256+ return nil , fmt .Errorf ("invalid time window boundary format: %q" , boundary )
257+ }
258+ startTime , err := time .Parse (time .RFC3339 , parts [0 ])
259+ if err != nil {
260+ return nil , fmt .Errorf ("invalid start time format: %q. expected format: %q" , parts [0 ], time .RFC3339 )
261+ }
262+ endTime , err := time .Parse (time .RFC3339 , parts [1 ])
263+ if err != nil {
264+ return nil , fmt .Errorf ("invalid end time format: %q. expected format: %q" , parts [1 ], time .RFC3339 )
265+ }
266+ if startTime .After (endTime ) {
267+ return nil , fmt .Errorf ("start time cannot be after end time: %q" , boundary )
268+ }
269+ boundaries = append (boundaries , TimeWindowBoundaries {
270+ StartTime : startTime ,
271+ EndTime : endTime ,
272+ })
273+ }
274+ return boundaries , nil
275+ }
0 commit comments