scheduler.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. export class CronScheduler {
  2. private timers: Map<string, ReturnType<typeof setInterval>> = new Map()
  3. parse(cron: string): { next: () => number } {
  4. const parts = cron.trim().split(/\s+/)
  5. if (parts.length !== 5) throw new Error(`Invalid cron: ${cron}`)
  6. const [min, hour, day, month, weekday] = parts
  7. const mins = this.parseField(min, 0, 59)
  8. const hours = this.parseField(hour, 0, 23)
  9. const days = this.parseField(day, 1, 31)
  10. const months = this.parseField(month, 1, 12)
  11. const weekdays = this.parseField(weekday, 0, 6)
  12. return {
  13. next: () => {
  14. const now = new Date()
  15. const candidates: number[] = []
  16. for (let m = 0; m < 60; m++) {
  17. for (let h = 0; h < 24; h++) {
  18. for (let d = 1; d <= 31; d++) {
  19. for (let mo = 1; mo <= 12; mo++) {
  20. if (mins.has(m) && hours.has(h) && days.has(d) && months.has(mo)) {
  21. const candidate = new Date(now.getFullYear(), mo - 1, d, h, m, 0, 0)
  22. if (candidate > now && weekdays.has(candidate.getDay())) {
  23. candidates.push(candidate.getTime())
  24. }
  25. }
  26. }
  27. }
  28. }
  29. }
  30. candidates.sort((a, b) => a - b)
  31. return candidates[0] || (now.getTime() + 60000)
  32. }
  33. }
  34. }
  35. private parseField(field: string, min: number, max: number): Set<number> {
  36. const result = new Set<number>()
  37. if (field === "*") {
  38. for (let i = min; i <= max; i++) result.add(i)
  39. return result
  40. }
  41. for (const part of field.split(",")) {
  42. if (part.includes("/")) {
  43. const [range, step] = part.split("/")
  44. const s = parseInt(step)
  45. const [rMin, rMax] = range === "*" ? [min, max] : range.split("-").map(Number)
  46. for (let i = rMin!; i <= (rMax ?? rMin!); i += s) {
  47. if (i >= min && i <= max) result.add(i)
  48. }
  49. } else if (part.includes("-")) {
  50. const [lo, hi] = part.split("-").map(Number)
  51. for (let i = lo!; i <= hi!; i++) {
  52. if (i >= min && i <= max) result.add(i)
  53. }
  54. } else {
  55. const v = parseInt(part)
  56. if (v >= min && v <= max) result.add(v)
  57. }
  58. }
  59. return result
  60. }
  61. start(name: string, intervalMs: number, fn: () => void): void {
  62. this.stop(name)
  63. const t = setInterval(fn, intervalMs)
  64. this.timers.set(name, t)
  65. }
  66. stop(name: string): void {
  67. const t = this.timers.get(name)
  68. if (t) {
  69. clearInterval(t)
  70. this.timers.delete(name)
  71. }
  72. }
  73. stopAll(): void {
  74. for (const t of this.timers.values()) clearInterval(t)
  75. this.timers.clear()
  76. }
  77. }