(cronExpression: string)
| 368 | * whether the input contained `L` and therefore needs runtime DOM filtering. |
| 369 | */ |
| 370 | export const expandCronLForNodeCron = (cronExpression: string): { expression: string; hasL: boolean } => { |
| 371 | const fields = cronExpression.trim().split(/\s+/) |
| 372 | if (fields.length !== 5 && fields.length !== 6) { |
| 373 | return { expression: cronExpression, hasL: false } |
| 374 | } |
| 375 | const domIdx = fields.length === 6 ? 3 : 2 |
| 376 | const domField = fields[domIdx] |
| 377 | const parts = domField.split(',') |
| 378 | const hasL = parts.includes('L') |
| 379 | if (!hasL) return { expression: cronExpression, hasL: false } |
| 380 | |
| 381 | // L expands to `28-31`, so drop any user-specified parts that are already |
| 382 | // covered by that range to avoid redundant entries like `31,28-31`. |
| 383 | // Ranges/steps that aren't fully inside [28, 31] are left untouched — |
| 384 | // node-cron will simply union them with the appended `28-31` part. |
| 385 | const kept: string[] = [] |
| 386 | for (const p of parts) { |
| 387 | if (p === 'L') continue |
| 388 | if (/^\d+$/.test(p)) { |
| 389 | const n = parseInt(p, 10) |
| 390 | if (n >= 28 && n <= 31) continue |
| 391 | } else { |
| 392 | const rangeMatch = /^(\d+)-(\d+)$/.exec(p) |
| 393 | if (rangeMatch) { |
| 394 | const a = parseInt(rangeMatch[1], 10) |
| 395 | const b = parseInt(rangeMatch[2], 10) |
| 396 | if (a >= 28 && b <= 31) continue |
| 397 | } |
| 398 | } |
| 399 | kept.push(p) |
| 400 | } |
| 401 | kept.push('28-31') |
| 402 | fields[domIdx] = kept.join(',') |
| 403 | return { expression: fields.join(' '), hasL: true } |
| 404 | } |
| 405 | |
| 406 | /** |
| 407 | * Verify that the given `date`'s day-of-month (interpreted in `timezone`) |
no test coverage detected