| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- export class CronScheduler {
- private timers: Map<string, ReturnType<typeof setInterval>> = new Map()
- parse(cron: string): { next: () => number } {
- const parts = cron.trim().split(/\s+/)
- if (parts.length !== 5) throw new Error(`Invalid cron: ${cron}`)
- const [min, hour, day, month, weekday] = parts
- const mins = this.parseField(min, 0, 59)
- const hours = this.parseField(hour, 0, 23)
- const days = this.parseField(day, 1, 31)
- const months = this.parseField(month, 1, 12)
- const weekdays = this.parseField(weekday, 0, 6)
- return {
- next: () => {
- const now = new Date()
- const candidates: number[] = []
- for (let m = 0; m < 60; m++) {
- for (let h = 0; h < 24; h++) {
- for (let d = 1; d <= 31; d++) {
- for (let mo = 1; mo <= 12; mo++) {
- if (mins.has(m) && hours.has(h) && days.has(d) && months.has(mo)) {
- const candidate = new Date(now.getFullYear(), mo - 1, d, h, m, 0, 0)
- if (candidate > now && weekdays.has(candidate.getDay())) {
- candidates.push(candidate.getTime())
- }
- }
- }
- }
- }
- }
- candidates.sort((a, b) => a - b)
- return candidates[0] || (now.getTime() + 60000)
- }
- }
- }
- private parseField(field: string, min: number, max: number): Set<number> {
- const result = new Set<number>()
- if (field === "*") {
- for (let i = min; i <= max; i++) result.add(i)
- return result
- }
- for (const part of field.split(",")) {
- if (part.includes("/")) {
- const [range, step] = part.split("/")
- const s = parseInt(step)
- const [rMin, rMax] = range === "*" ? [min, max] : range.split("-").map(Number)
- for (let i = rMin!; i <= (rMax ?? rMin!); i += s) {
- if (i >= min && i <= max) result.add(i)
- }
- } else if (part.includes("-")) {
- const [lo, hi] = part.split("-").map(Number)
- for (let i = lo!; i <= hi!; i++) {
- if (i >= min && i <= max) result.add(i)
- }
- } else {
- const v = parseInt(part)
- if (v >= min && v <= max) result.add(v)
- }
- }
- return result
- }
- start(name: string, intervalMs: number, fn: () => void): void {
- this.stop(name)
- const t = setInterval(fn, intervalMs)
- this.timers.set(name, t)
- }
- stop(name: string): void {
- const t = this.timers.get(name)
- if (t) {
- clearInterval(t)
- this.timers.delete(name)
- }
- }
- stopAll(): void {
- for (const t of this.timers.values()) clearInterval(t)
- this.timers.clear()
- }
- }
|