214
}
215
216
>
func parseCalendarToStructured(cal *schedulepb.CalendarSpec) (*schedulepb.StructuredCalendarSpec, error) {
calendar.go
217
>
var errs []string
218
>
makeRangeOrNil := func(s, field, def string, minVal, maxVal int, parseMode parseMode) []*schedulepb.Range {
219
>
r, err := makeRange(s, field, def, minVal, maxVal, parseMode)
220
>
if err != nil {
222
>
}
224
}
225
>
ss := &schedulepb.StructuredCalendarSpec{
calendar.go
226
>
Second: makeRangeOrNil(cal.Second, "Second", "0", 0, 59, parseModeInt),
227
>
Minute: makeRangeOrNil(cal.Minute, "Minute", "0", 0, 59, parseModeInt),
228
>
Hour: makeRangeOrNil(cal.Hour, "Hour", "0", 0, 23, parseModeInt),
229
>
DayOfWeek: makeRangeOrNil(cal.DayOfWeek, "DayOfWeek", "*", 0, 7, parseModeDow),
230
>
DayOfMonth: makeRangeOrNil(cal.DayOfMonth, "DayOfMonth", "*", 1, 31, parseModeInt),
231
>
Month: makeRangeOrNil(cal.Month, "Month", "*", 1, 12, parseModeMonth),
232
>
Year: makeRangeOrNil(cal.Year, "Year", "*", minCalendarYear, maxCalendarYear, parseModeYear),
233
>
Comment: cal.Comment,
234
>
}
235
>
if len(errs) > 0 {
236
>
return nil, errors.New(strings.Join(errs, ", "))
calendar.go
237
>
}
239
}
240
241
>
func parseCronString(c string) (*schedulepb.StructuredCalendarSpec, *schedulepb.IntervalSpec, string, error) {
calendar.go
242
>
var tzName string
243
>
var comment string
244
>
245
>
c = strings.TrimSpace(c)
246
>
247
>
// split out timezone
248
>
if strings.HasPrefix(c, "TZ=") || strings.HasPrefix(c, "CRON_TZ=") {
249
>
tz, rest, found := strings.Cut(c, " ")
calendar.go
250
>
if !found {
251
return nil, nil, "", errors.New("CronString has time zone but missing fields")
252
}
254
>
_, tzName, _ = strings.Cut(tz, "=")
255
}
256
257
// split out comment
259
>
c = strings.TrimSpace(c)
260
>
comment = strings.TrimSpace(comment)
261
>
262
>
// handle @every intervals
263
>
if strings.HasPrefix(c, "@every") {
264
>
iv, err := parseCronStringInterval(c)
calendar.go
265
>
return nil, iv, "", err
266
>
}
267
268
// handle @hourly, etc.
270
>
271
>
// split fields
272
>
cal := schedulepb.CalendarSpec{Comment: comment}
273
>
// Use FieldsSeq to avoid building an unbounded slice; we only accept 5–7 fields.
274
>
const maxCronFields = 7
275
>
var toks [maxCronFields]string
276
>
n := 0
277
>
for tok := range strings.FieldsSeq(c) {
278
>
if n < maxCronFields {
279
>
toks[n] = tok
280
>
n++
281
>
continue
282
}
283
// More than 7 fields → invalid.
284
return nil, nil, "", errors.New("CronString does not have 5-7 fields")
285
}
288
>
cal.Minute, cal.Hour, cal.DayOfMonth, cal.Month, cal.DayOfWeek = toks[0], toks[1], toks[2], toks[3], toks[4]
290
>
cal.Minute, cal.Hour, cal.DayOfMonth, cal.Month, cal.DayOfWeek, cal.Year = toks[0], toks[1], toks[2], toks[3], toks[4], toks[5]
292
>
cal.Second, cal.Minute, cal.Hour, cal.DayOfMonth, cal.Month, cal.DayOfWeek, cal.Year = toks[0], toks[1], toks[2], toks[3], toks[4], toks[5], toks[6]
293
default:
294
return nil, nil, "", errors.New("CronString does not have 5-7 fields")
295
}
296
297
>
structured, err := parseCalendarToStructured(&cal)
calendar.go
298
>
if err != nil {
300
>
}
301
303
}
304
305
>
func parseCronStringInterval(c string) (*schedulepb.IntervalSpec, error) {
calendar.go
306
>
// split after @every
307
>
_, interval, found := strings.Cut(c, " ")
308
>
if !found {
309
return nil, errors.New("CronString does not have interval after @every")
310
}
311
// allow @every 14h/3h
312
>
interval, phase, _ := strings.Cut(interval, "/")
calendar.go
313
>
intervalDuration, err := timestamp.ParseDuration(interval)
314
>
if err != nil {
315
return nil, err
316
}
318
>
return &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration)}, nil
319
>
}
320
>
phaseDuration, err := timestamp.ParseDuration(phase)
calendar.go
321
>
if err != nil {
322
return nil, err
323
}
324
>
return &schedulepb.IntervalSpec{Interval: durationpb.New(intervalDuration), Phase: durationpb.New(phaseDuration)}, nil
calendar.go
325
}
326
327
>
func handlePredefinedCronStrings(c string) string {
calendar.go
328
>
switch c {
329
case "@yearly", "@annually":
330
return "0 0 1 1 *"
332
>
return "0 0 1 * *"
333
case "@weekly":
334
return "0 0 * * 0"