Skip to content

Commit e776d42

Browse files
committed
feat: add support for yearly time windows and implement time boundaries validation
1 parent eff6c02 commit e776d42

2 files changed

Lines changed: 900 additions & 0 deletions

File tree

common-lib/utils/TimeUtils.go

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

33
import (
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

4547
func (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

Comments
 (0)