(field: string, range: FieldRange)
| 29 | // Supports: wildcard, N, star-slash-N (step), N-M (range), and comma-lists. |
| 30 | // Returns null if invalid. |
| 31 | function expandField(field: string, range: FieldRange): number[] | null { |
| 32 | const { min, max } = range |
| 33 | const out = new Set<number>() |
| 34 | |
| 35 | for (const part of field.split(',')) { |
| 36 | // wildcard or star-slash-N |
| 37 | const stepMatch = part.match(/^\*(?:\/(\d+))?$/) |
| 38 | if (stepMatch) { |
| 39 | const step = stepMatch[1] ? parseInt(stepMatch[1], 10) : 1 |
| 40 | if (step < 1) return null |
| 41 | for (let i = min; i <= max; i += step) out.add(i) |
| 42 | continue |
| 43 | } |
| 44 | |
| 45 | // N-M or N-M/S |
| 46 | const rangeMatch = part.match(/^(\d+)-(\d+)(?:\/(\d+))?$/) |
| 47 | if (rangeMatch) { |
| 48 | const lo = parseInt(rangeMatch[1]!, 10) |
| 49 | const hi = parseInt(rangeMatch[2]!, 10) |
| 50 | const step = rangeMatch[3] ? parseInt(rangeMatch[3], 10) : 1 |
| 51 | // dayOfWeek: accept 7 as Sunday alias in ranges (e.g. 5-7 = Fri,Sat,Sun → [5,6,0]) |
| 52 | const isDow = min === 0 && max === 6 |
| 53 | const effMax = isDow ? 7 : max |
| 54 | if (lo > hi || step < 1 || lo < min || hi > effMax) return null |
| 55 | for (let i = lo; i <= hi; i += step) { |
| 56 | out.add(isDow && i === 7 ? 0 : i) |
| 57 | } |
| 58 | continue |
| 59 | } |
| 60 | |
| 61 | // plain N |
| 62 | const singleMatch = part.match(/^\d+$/) |
| 63 | if (singleMatch) { |
| 64 | let n = parseInt(part, 10) |
| 65 | // dayOfWeek: accept 7 as Sunday alias → 0 |
| 66 | if (min === 0 && max === 6 && n === 7) n = 0 |
| 67 | if (n < min || n > max) return null |
| 68 | out.add(n) |
| 69 | continue |
| 70 | } |
| 71 | |
| 72 | return null |
| 73 | } |
| 74 | |
| 75 | if (out.size === 0) return null |
| 76 | return Array.from(out).sort((a, b) => a - b) |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Parse a 5-field cron expression into expanded number arrays. |
no test coverage detected