@@ -2,7 +2,6 @@ package cron
2
2
3
3
import (
4
4
"fmt"
5
- "log"
6
5
"math"
7
6
"strconv"
8
7
"strings"
@@ -17,36 +16,46 @@ import (
17
16
// It accepts
18
17
// - Standard crontab specs, e.g. "* * * * ?"
19
18
// - Descriptors, e.g. "@midnight", "@every 1h30m"
20
- func ParseStandard (standardSpec string ) (_ Schedule , err error ) {
21
- // Convert panics into errors
22
- defer func () {
23
- if recovered := recover (); recovered != nil {
24
- err = fmt .Errorf ("%v" , recovered )
25
- }
26
- }()
27
-
19
+ func ParseStandard (standardSpec string ) (Schedule , error ) {
28
20
if standardSpec [0 ] == '@' {
29
- return parseDescriptor (standardSpec ), nil
21
+ return parseDescriptor (standardSpec )
30
22
}
31
23
32
24
// Split on whitespace. We require exactly 5 fields.
33
25
// (minute) (hour) (day of month) (month) (day of week)
34
26
fields := strings .Fields (standardSpec )
35
27
if len (fields ) != 5 {
36
- log . Panicf ("Expected exactly 5, found %d: %s" , len (fields ), standardSpec )
28
+ return nil , fmt . Errorf ("Expected exactly 5 fields , found %d: %s" , len (fields ), standardSpec )
37
29
}
38
30
39
- schedule := & SpecSchedule {
40
- Second : 1 << seconds .min ,
41
- Minute : getField (fields [0 ], minutes ),
42
- Hour : getField (fields [1 ], hours ),
43
- Dom : getField (fields [2 ], dom ),
44
- Month : getField (fields [3 ], months ),
45
- Dow : getField (fields [4 ], dow ),
31
+ var err error
32
+ field := func (field string , r bounds ) uint64 {
33
+ if err != nil {
34
+ return uint64 (0 )
35
+ }
36
+ var bits uint64
37
+ bits , err = getField (field , r )
38
+ return bits
39
+ }
40
+ var (
41
+ minute = field (fields [0 ], minutes )
42
+ hour = field (fields [1 ], hours )
43
+ dayofmonth = field (fields [2 ], dom )
44
+ month = field (fields [3 ], months )
45
+ dayofweek = field (fields [4 ], dow )
46
+ )
47
+ if err != nil {
48
+ return nil , err
46
49
}
47
50
48
- return schedule , nil
49
-
51
+ return & SpecSchedule {
52
+ Second : 1 << seconds .min ,
53
+ Minute : minute ,
54
+ Hour : hour ,
55
+ Dom : dayofmonth ,
56
+ Month : month ,
57
+ Dow : dayofweek ,
58
+ }, nil
50
59
}
51
60
52
61
// Parse returns a new crontab schedule representing the given spec.
@@ -55,63 +64,81 @@ func ParseStandard(standardSpec string) (_ Schedule, err error) {
55
64
// It accepts
56
65
// - Full crontab specs, e.g. "* * * * * ?"
57
66
// - Descriptors, e.g. "@midnight", "@every 1h30m"
58
- func Parse (spec string ) (_ Schedule , err error ) {
59
- // Convert panics into errors
60
- defer func () {
61
- if recovered := recover (); recovered != nil {
62
- err = fmt .Errorf ("%v" , recovered )
63
- }
64
- }()
65
-
67
+ func Parse (spec string ) (Schedule , error ) {
66
68
if spec [0 ] == '@' {
67
- return parseDescriptor (spec ), nil
69
+ return parseDescriptor (spec )
68
70
}
69
71
70
72
// Split on whitespace. We require 5 or 6 fields.
71
73
// (second) (minute) (hour) (day of month) (month) (day of week, optional)
72
74
fields := strings .Fields (spec )
73
75
if len (fields ) != 5 && len (fields ) != 6 {
74
- log . Panicf ("Expected 5 or 6 fields, found %d: %s" , len (fields ), spec )
76
+ return nil , fmt . Errorf ("Expected 5 or 6 fields, found %d: %s" , len (fields ), spec )
75
77
}
76
78
77
79
// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
78
80
if len (fields ) == 5 {
79
81
fields = append (fields , "*" )
80
82
}
81
83
82
- schedule := & SpecSchedule {
83
- Second : getField (fields [0 ], seconds ),
84
- Minute : getField (fields [1 ], minutes ),
85
- Hour : getField (fields [2 ], hours ),
86
- Dom : getField (fields [3 ], dom ),
87
- Month : getField (fields [4 ], months ),
88
- Dow : getField (fields [5 ], dow ),
84
+ var err error
85
+ field := func (field string , r bounds ) uint64 {
86
+ if err != nil {
87
+ return uint64 (0 )
88
+ }
89
+ var bits uint64
90
+ bits , err = getField (field , r )
91
+ return bits
92
+ }
93
+ var (
94
+ second = field (fields [0 ], seconds )
95
+ minute = field (fields [1 ], minutes )
96
+ hour = field (fields [2 ], hours )
97
+ dayofmonth = field (fields [3 ], dom )
98
+ month = field (fields [4 ], months )
99
+ dayofweek = field (fields [5 ], dow )
100
+ )
101
+ if err != nil {
102
+ return nil , err
89
103
}
90
104
91
- return schedule , nil
105
+ return & SpecSchedule {
106
+ Second : second ,
107
+ Minute : minute ,
108
+ Hour : hour ,
109
+ Dom : dayofmonth ,
110
+ Month : month ,
111
+ Dow : dayofweek ,
112
+ }, nil
92
113
}
93
114
94
115
// getField returns an Int with the bits set representing all of the times that
95
- // the field represents. A "field" is a comma-separated list of "ranges".
96
- func getField ( field string , r bounds ) uint64 {
97
- // list = range {"," range}
116
+ // the field represents or error parsing field value . A "field" is a comma-separated
117
+ // list of "ranges".
118
+ func getField ( field string , r bounds ) ( uint64 , error ) {
98
119
var bits uint64
99
120
ranges := strings .FieldsFunc (field , func (r rune ) bool { return r == ',' })
100
121
for _ , expr := range ranges {
101
- bits |= getRange (expr , r )
122
+ bit , err := getRange (expr , r )
123
+ if err != nil {
124
+ return bits , err
125
+ }
126
+ bits |= bit
102
127
}
103
- return bits
128
+ return bits , nil
104
129
}
105
130
106
131
// getRange returns the bits indicated by the given expression:
107
132
// number | number "-" number [ "/" number ]
108
- func getRange ( expr string , r bounds ) uint64 {
109
-
133
+ // or error parsing range.
134
+ func getRange ( expr string , r bounds ) ( uint64 , error ) {
110
135
var (
111
136
start , end , step uint
112
137
rangeAndStep = strings .Split (expr , "/" )
113
138
lowAndHigh = strings .Split (rangeAndStep [0 ], "-" )
114
139
singleDigit = len (lowAndHigh ) == 1
140
+ err error
141
+ zero = uint64 (0 )
115
142
)
116
143
117
144
var extra_star uint64
@@ -120,68 +147,77 @@ func getRange(expr string, r bounds) uint64 {
120
147
end = r .max
121
148
extra_star = starBit
122
149
} else {
123
- start = parseIntOrName (lowAndHigh [0 ], r .names )
150
+ start , err = parseIntOrName (lowAndHigh [0 ], r .names )
151
+ if err != nil {
152
+ return zero , err
153
+ }
124
154
switch len (lowAndHigh ) {
125
155
case 1 :
126
156
end = start
127
157
case 2 :
128
- end = parseIntOrName (lowAndHigh [1 ], r .names )
158
+ end , err = parseIntOrName (lowAndHigh [1 ], r .names )
159
+ if err != nil {
160
+ return zero , err
161
+ }
129
162
default :
130
- log . Panicf ("Too many hyphens: %s" , expr )
163
+ return zero , fmt . Errorf ("Too many hyphens: %s" , expr )
131
164
}
132
165
}
133
166
134
167
switch len (rangeAndStep ) {
135
168
case 1 :
136
169
step = 1
137
170
case 2 :
138
- step = mustParseInt (rangeAndStep [1 ])
171
+ step , err = mustParseInt (rangeAndStep [1 ])
172
+ if err != nil {
173
+ return zero , err
174
+ }
139
175
140
176
// Special handling: "N/step" means "N-max/step".
141
177
if singleDigit {
142
178
end = r .max
143
179
}
144
180
default :
145
- log . Panicf ("Too many slashes: %s" , expr )
181
+ return zero , fmt . Errorf ("Too many slashes: %s" , expr )
146
182
}
147
183
148
184
if start < r .min {
149
- log . Panicf ("Beginning of range (%d) below minimum (%d): %s" , start , r .min , expr )
185
+ return zero , fmt . Errorf ("Beginning of range (%d) below minimum (%d): %s" , start , r .min , expr )
150
186
}
151
187
if end > r .max {
152
- log . Panicf ("End of range (%d) above maximum (%d): %s" , end , r .max , expr )
188
+ return zero , fmt . Errorf ("End of range (%d) above maximum (%d): %s" , end , r .max , expr )
153
189
}
154
190
if start > end {
155
- log . Panicf ("Beginning of range (%d) beyond end of range (%d): %s" , start , end , expr )
191
+ return zero , fmt . Errorf ("Beginning of range (%d) beyond end of range (%d): %s" , start , end , expr )
156
192
}
157
193
if step == 0 {
158
- log . Panicf ("Step of range should be a positive number: %s" , expr )
194
+ return zero , fmt . Errorf ("Step of range should be a positive number: %s" , expr )
159
195
}
160
196
161
- return getBits (start , end , step ) | extra_star
197
+ return getBits (start , end , step ) | extra_star , nil
162
198
}
163
199
164
200
// parseIntOrName returns the (possibly-named) integer contained in expr.
165
- func parseIntOrName (expr string , names map [string ]uint ) uint {
201
+ func parseIntOrName (expr string , names map [string ]uint ) ( uint , error ) {
166
202
if names != nil {
167
203
if namedInt , ok := names [strings .ToLower (expr )]; ok {
168
- return namedInt
204
+ return namedInt , nil
169
205
}
170
206
}
171
207
return mustParseInt (expr )
172
208
}
173
209
174
- // mustParseInt parses the given expression as an int or panics .
175
- func mustParseInt (expr string ) uint {
210
+ // mustParseInt parses the given expression as an int or returns an error .
211
+ func mustParseInt (expr string ) ( uint , error ) {
176
212
num , err := strconv .Atoi (expr )
177
213
if err != nil {
178
- log . Panicf ("Failed to parse int from %s: %s" , expr , err )
214
+ return 0 , fmt . Errorf ("Failed to parse int from %s: %s" , expr , err )
179
215
}
180
216
if num < 0 {
181
- log . Panicf ("Negative number (%d) not allowed: %s" , num , expr )
217
+ return 0 , fmt . Errorf ("Negative number (%d) not allowed: %s" , num , expr )
182
218
}
183
219
184
- return uint (num )
220
+ return uint (num ), nil
185
221
}
186
222
187
223
// getBits sets all bits in the range [min, max], modulo the given step size.
@@ -205,10 +241,9 @@ func all(r bounds) uint64 {
205
241
return getBits (r .min , r .max , 1 ) | starBit
206
242
}
207
243
208
- // parseDescriptor returns a pre-defined schedule for the expression, or panics
209
- // if none matches.
210
- func parseDescriptor (spec string ) Schedule {
211
- switch spec {
244
+ // parseDescriptor returns a predefined schedule for the expression, or error if none matches.
245
+ func parseDescriptor (descriptor string ) (Schedule , error ) {
246
+ switch descriptor {
212
247
case "@yearly" , "@annually" :
213
248
return & SpecSchedule {
214
249
Second : 1 << seconds .min ,
@@ -217,7 +252,7 @@ func parseDescriptor(spec string) Schedule {
217
252
Dom : 1 << dom .min ,
218
253
Month : 1 << months .min ,
219
254
Dow : all (dow ),
220
- }
255
+ }, nil
221
256
222
257
case "@monthly" :
223
258
return & SpecSchedule {
@@ -227,7 +262,7 @@ func parseDescriptor(spec string) Schedule {
227
262
Dom : 1 << dom .min ,
228
263
Month : all (months ),
229
264
Dow : all (dow ),
230
- }
265
+ }, nil
231
266
232
267
case "@weekly" :
233
268
return & SpecSchedule {
@@ -237,7 +272,7 @@ func parseDescriptor(spec string) Schedule {
237
272
Dom : all (dom ),
238
273
Month : all (months ),
239
274
Dow : 1 << dow .min ,
240
- }
275
+ }, nil
241
276
242
277
case "@daily" , "@midnight" :
243
278
return & SpecSchedule {
@@ -247,7 +282,7 @@ func parseDescriptor(spec string) Schedule {
247
282
Dom : all (dom ),
248
283
Month : all (months ),
249
284
Dow : all (dow ),
250
- }
285
+ }, nil
251
286
252
287
case "@hourly" :
253
288
return & SpecSchedule {
@@ -257,18 +292,17 @@ func parseDescriptor(spec string) Schedule {
257
292
Dom : all (dom ),
258
293
Month : all (months ),
259
294
Dow : all (dow ),
260
- }
295
+ }, nil
261
296
}
262
297
263
298
const every = "@every "
264
- if strings .HasPrefix (spec , every ) {
265
- duration , err := time .ParseDuration (spec [len (every ):])
299
+ if strings .HasPrefix (descriptor , every ) {
300
+ duration , err := time .ParseDuration (descriptor [len (every ):])
266
301
if err != nil {
267
- log . Panicf ("Failed to parse duration %s: %s" , spec , err )
302
+ return nil , fmt . Errorf ("Failed to parse duration %s: %s" , descriptor , err )
268
303
}
269
- return Every (duration )
304
+ return Every (duration ), nil
270
305
}
271
306
272
- log .Panicf ("Unrecognized descriptor: %s" , spec )
273
- return nil
307
+ return nil , fmt .Errorf ("Unrecognized descriptor: %s" , descriptor )
274
308
}
0 commit comments