(expr: string)
| 228 | * Validate a cron expression |
| 229 | */ |
| 230 | export function isValidCron(expr: string): boolean { |
| 231 | const parts = parseCron(expr); |
| 232 | if (!parts) return false; |
| 233 | |
| 234 | const validateField = (field: string, min: number, max: number): boolean => { |
| 235 | if (field === '*') return true; |
| 236 | |
| 237 | // Handle step values (*/n or n/m) |
| 238 | if (field.includes('/')) { |
| 239 | const [range, step] = field.split('/'); |
| 240 | if (range !== '*' && !validateField(range, min, max)) return false; |
| 241 | const stepNum = parseInt(step); |
| 242 | return !isNaN(stepNum) && stepNum > 0; |
| 243 | } |
| 244 | |
| 245 | // Handle ranges (n-m) |
| 246 | if (field.includes('-')) { |
| 247 | const [start, end] = field.split('-').map(Number); |
| 248 | return !isNaN(start) && !isNaN(end) && start >= min && end <= max && start <= end; |
| 249 | } |
| 250 | |
| 251 | // Handle lists (n,m,...) |
| 252 | if (field.includes(',')) { |
| 253 | return field.split(',').every(v => { |
| 254 | const num = parseInt(v); |
| 255 | return !isNaN(num) && num >= min && num <= max; |
| 256 | }); |
| 257 | } |
| 258 | |
| 259 | // Single value |
| 260 | const num = parseInt(field); |
| 261 | return !isNaN(num) && num >= min && num <= max; |
| 262 | }; |
| 263 | |
| 264 | return ( |
| 265 | validateField(parts.minute, 0, 59) && |
| 266 | validateField(parts.hour, 0, 23) && |
| 267 | validateField(parts.dayOfMonth, 1, 31) && |
| 268 | validateField(parts.month, 1, 12) && |
| 269 | validateField(parts.dayOfWeek, 0, 6) |
| 270 | ); |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Common cron presets |
no test coverage detected