-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcronexpr.go
More file actions
227 lines (205 loc) · 5.48 KB
/
Copy pathcronexpr.go
File metadata and controls
227 lines (205 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Package cronexpr parses cron time expressions.
package cronexpr
import (
"errors"
"slices"
"strings"
"time"
)
// Expression represents a parsed cron expression. Use Parse or MustParse to create one.
type Expression struct {
normalized string // alias-expanded cron string, stored by Parse() for Describe()
secondList []int
minuteList []int
hourList []int
daysOfMonth map[int]bool
workdaysOfMonth map[int]bool
lastDayOfMonth bool
lastWorkdayOfMonth bool
daysOfMonthRestricted bool
actualDaysOfMonthList []int
monthList []int
daysOfWeek map[int]bool
specificWeekDaysOfWeek map[int]bool
lastWeekDaysOfWeek map[int]bool
daysOfWeekRestricted bool
yearList []int
}
// MustParse returns a new Expression pointer. It expects a well-formed cron
// expression. If a malformed cron expression is supplied, it will panic.
func MustParse(cronLine string) *Expression {
expr, err := Parse(cronLine)
if err != nil {
panic(err)
}
return expr
}
// Parse returns a new Expression pointer. An error is returned if a malformed
// cron expression is supplied.
func Parse(cronLine string) (*Expression, error) {
// Maybe one of the built-in aliases is being used
cron := cronNormalizer.Replace(cronLine)
const (
minCronFields = 5
maxCronFields = 7
)
fields := strings.Fields(cron)
fieldCount := len(fields)
if fieldCount < minCronFields {
return nil, errors.New("missing field(s)")
}
// ignore fields beyond 7th
if fieldCount > maxCronFields {
fieldCount = maxCronFields
}
var expr Expression
expr.normalized = cron
var field = 0
var err error
// second field (optional)
if fieldCount == maxCronFields {
err = parseField(fields[field], secondDescriptor, &expr.secondList)
if err != nil {
return nil, err
}
field++
} else {
expr.secondList = []int{0}
}
// minute field
err = parseField(fields[field], minuteDescriptor, &expr.minuteList)
if err != nil {
return nil, err
}
field++
// hour field
err = parseField(fields[field], hourDescriptor, &expr.hourList)
if err != nil {
return nil, err
}
field++
// day of month field
err = expr.domFieldHandler(fields[field])
if err != nil {
return nil, err
}
field++
// month field
err = parseField(fields[field], monthDescriptor, &expr.monthList)
if err != nil {
return nil, err
}
field++
// day of week field
err = expr.dowFieldHandler(fields[field])
if err != nil {
return nil, err
}
field++
// year field
if field < fieldCount {
err = parseField(fields[field], yearDescriptor, &expr.yearList)
if err != nil {
return nil, err
}
} else {
expr.yearList = yearDescriptor.defaultList
}
return &expr, nil
}
// Next returns the closest time instant immediately following fromTime which
// matches the cron expression.
//
// The time.Location of the returned time instant is the same as that of
// fromTime.
//
// The zero value of time.Time is returned if no matching time instant exists
// or if fromTime is itself a zero value.
func (expr *Expression) Next(fromTime time.Time) time.Time {
// Special case
if fromTime.IsZero() {
return fromTime
}
// Walk each field from year down to second. If any field doesn't match,
// advance to the next matching time for that field.
// year
v := fromTime.Year()
i, _ := slices.BinarySearch(expr.yearList, v)
if i == len(expr.yearList) {
return time.Time{}
}
if v != expr.yearList[i] {
return expr.nextYear(fromTime)
}
// month
v = int(fromTime.Month())
i, _ = slices.BinarySearch(expr.monthList, v)
if i == len(expr.monthList) {
return expr.nextYear(fromTime)
}
if v != expr.monthList[i] {
return expr.nextMonth(fromTime)
}
expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(fromTime.Year(), int(fromTime.Month()))
if len(expr.actualDaysOfMonthList) == 0 {
return expr.nextMonth(fromTime)
}
// day of month
v = fromTime.Day()
i, _ = slices.BinarySearch(expr.actualDaysOfMonthList, v)
if i == len(expr.actualDaysOfMonthList) {
return expr.nextMonth(fromTime)
}
if v != expr.actualDaysOfMonthList[i] {
return expr.nextDayOfMonth(fromTime)
}
// hour
v = fromTime.Hour()
i, _ = slices.BinarySearch(expr.hourList, v)
if i == len(expr.hourList) {
return expr.nextDayOfMonth(fromTime)
}
if v != expr.hourList[i] {
return expr.nextHour(fromTime)
}
// minute
v = fromTime.Minute()
i, _ = slices.BinarySearch(expr.minuteList, v)
if i == len(expr.minuteList) {
return expr.nextHour(fromTime)
}
if v != expr.minuteList[i] {
return expr.nextMinute(fromTime)
}
// second
v = fromTime.Second()
i, _ = slices.BinarySearch(expr.secondList, v)
if i == len(expr.secondList) {
return expr.nextMinute(fromTime)
}
return expr.nextSecond(fromTime)
}
// NextN returns a slice of the n closest time instants immediately following
// fromTime which match the cron expression.
//
// The time instants in the returned slice are in chronological ascending order.
// The time.Location of the returned time instants is the same as that of
// fromTime.
//
// A slice with length between 0 and n is returned; if not enough matching
// time instants exist, the number of returned entries will be less than n.
func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time {
nextTimes := make([]time.Time, 0, n)
if n > 0 {
fromTime = expr.Next(fromTime)
for !fromTime.IsZero() {
nextTimes = append(nextTimes, fromTime)
n--
if n == 0 {
break
}
fromTime = expr.nextSecond(fromTime)
}
}
return nextTimes
}