8000 feat: define functions for validation of schedules · coder/coder@0565fb7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0565fb7

Browse files
feat: define functions for validation of schedules
1 parent b3130a8 commit 0565fb7

File tree

2 files changed

+578
-0
lines changed

2 files changed

+578
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package cron
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
7+
"golang.org/x/xerrors"
8+
)
9+
10+
// ValidateSchedules checks if any schedules overlap
11+
func ValidateSchedules(schedules []*Schedule) error {
12+
for i := 0; i < len(schedules); i++ {
13+
for j := i + 1; j < len(schedules); j++ {
14+
overlap, err := SchedulesOverlap(schedules[i], schedules[j])
15+
if err != nil {
16+
return xerrors.Errorf("invalid schedule: %w", err)
17+
}
18+
if overlap {
19+
return xerrors.Errorf("schedules overlap: %s and %s",
20+
schedules[i].Cron(), schedules[j].Cron())
21+
}
22+
}
23+
}
24+
return nil
25+
}
26+
27+
// SchedulesOverlap checks if two schedules overlap by checking
28+
// days, months, and hours separately
29+
func SchedulesOverlap(s1, s2 *Schedule) (bool, error) {
30+
// Get cron fields
31+
fields1 := strings.Fields(s1.Cron())
32+
fields2 := strings.Fields(s2.Cron())
33+
34+
// Check if months overlap
35+
monthsOverlap, err := MonthsOverlap(fields1[3], fields2[3])
36+
if err != nil {
37+
return false, xerrors.Errorf("invalid month range: %w", err)
38+
}
39+
if !monthsOverlap {
40+
return false, nil
41+
}
42+
43+
// Check if days overlap (DOM OR DOW)
44+
daysOverlap, err := DaysOverlap(fields1[2], fields1[4], fields2[2], fields2[4])
45+
if err != nil {
46+
return false, xerrors.Errorf("invalid day range: %w", err)
47+
}
48+
if !daysOverlap {
49+
return false, nil
50+
}
51+
52+
// Check if hours overlap
53+
hoursOverlap, err := HoursOverlap(fields1[1], fields2[1])
54+
if err != nil {
55+
return false, xerrors.Errorf("invalid hour range: %w", err)
56+
}
57+
58+
return hoursOverlap, nil
59+
}
60+
61+
// MonthsOverlap checks if two month ranges overlap
62+
func MonthsOverlap(months1, months2 string) (bool, error) {
63+
return CheckOverlap(months1, months2, 12)
64+
}
65+
66+
// HoursOverlap checks if two hour ranges overlap
67+
func HoursOverlap(hours1, hours2 string) (bool, error) {
68+
return CheckOverlap(hours1, hours2, 23)
69+
}
70+
71+
// DomOverlap checks if two day-of-month ranges overlap
72+
func DomOverlap(dom1, dom2 string) (bool, error) {
73+
return CheckOverlap(dom1, dom2, 31)
74+
}
75+
76+
// DowOverlap checks if two day-of-week ranges overlap
77+
func DowOverlap(dow1, dow2 string) (bool, error) {
78+
return CheckOverlap(dow1, dow2, 6)
79+
}
80+
81+
// DaysOverlap checks if two day ranges overlap, considering both DOM and DOW.
82+
// Returns true if either DOM ranges overlap OR DOW ranges overlap.
83+
func DaysOverlap(dom1, dow1, dom2, dow2 string) (bool, error) {
84+
// Check if either DOM or DOW overlaps
85+
domOverlap, err := DomOverlap(dom1, dom2)
86+
if err != nil {
87+
return false, err
88+
}
89+
dowOverlap, err := DowOverlap(dow1, dow2)
90+
if err != nil {
91+
return false, err
92+
}
93+
94+
return domOverlap || dowOverlap, nil
95+
}
96+
97+
// CheckOverlap is a generic function to check if two ranges overlap
98+
func CheckOverlap(range1, range2 string, maxValue int) (bool, error) {
99+
set1, err := ParseRange(range1, maxValue)
100+
if err != nil {
101+
return false, err
102+
}
103+
set2, err := ParseRange(range2, maxValue)
104+
if err != nil {
105+
return false, err
106+
}
107+
108+
for value := range set1 {
109+
if set2[value] {
110+
return true, nil
111+
}
112+
}
113+
return false, nil
114+
}
115+
116+
// ParseRange converts a cron range to a set of integers
117+
// maxValue is the maximum allowed value (e.g., 23 for hours, 6 for DOW, 12 for months, 31 for DOM)
118+
func ParseRange(input string, maxValue int) (map[int]bool, error) {
119+
result := make(map[int]bool)
< A3D4 code>120+
121+
// Handle "*" case
122+
if input == "*" {
123+
for i := 0; i <= maxValue; i++ {
124+
result[i] = true
125+
}
126+
return result, nil
127+
}
128+
129+
// Parse ranges like "1-3,5,7-9"
130+
parts := strings.Split(input, ",")
131+
for _, part := range parts {
132+
if strings.Contains(part, "-") {
133+
// Handle range like "1-3"
134+
rangeParts := strings.Split(part, "-")
135+
start, err := strconv.Atoi(rangeParts[0])
136+
if err != nil {
137+
return nil, xerrors.Errorf("invalid start value in range: %w", err)
138+
}
139+
end, err := strconv.Atoi(rangeParts[1])
140+
if err != nil {
141+
return nil, xerrors.Errorf("invalid end value in range: %w", err)
142+
}
143+
144+
// Validate range
145+
if start < 0 || end > maxValue || start > end {
146+
return nil, xerrors.Errorf("invalid range %d-%d: values must be between 0 and %d", start, end, maxValue)
147+
}
148+
149+
for i := start; i <= end; i++ {
150+
result[i] = true
151+
}
152+
} else {
153+
// Handle single value
154+
value, err := strconv.Atoi(part)
155+
if err != nil {
156+
return nil, xerrors.Errorf("invalid value: %w", err)
157+
}
158+
159+
// Validate value
160+
if value < 0 || value > maxValue {
161+
return nil, xerrors.Errorf("invalid value %d: must be between 0 and %d", value, maxValue)
162+
}
163+
164+
result[value] = true
165+
}
166+
}
167+
return result, nil
168+
}

0 commit comments

Comments
 (0)
0