UNPKG

timed-silky

Version:

Timed-Silky 是基于一款基于 TypeScript 的定时任务调度器。得益于 TypeScript,Timed-Silky 可以用符合特定句式的自然语言描述任务的调度规则,提供丝滑的链式调用接口。

354 lines (353 loc) 11.8 kB
import { TimePoint } from "./time-type"; /** * 将时间戳转成Date 对象 * @param timestamp 毫秒表示的时间戳 * @returns Date 对象 */ function convertNumber2Date(timestamp) { if (timestamp < 1000000000) { timestamp = 0; } else if (timestamp < 10000000000) { timestamp *= 1000; } return new Date(timestamp); } // 将调度规则分为两部分:开头(Head)为固定时间点、结尾(Tail)为所在时间范围 class Head { /** * 设置 Head 对象连接的 Tail * @param rule 可以连接到 Head 对象后面的 Tail 对象 */ setNextRule(rule) { this.nextRule = rule; } } /** * 固定时间点规则,例如每天的 21:30,周六的 22:00 */ class TimePointRule extends Head { constructor(points) { super(); this.points = []; if (Array.isArray(points)) { this.addPoints(points); } else { this.points.push(points); } } addPoint(point) { this.points.push(point); this.points.sort(TimePoint.compare); return this; } addPoints(points) { this.points.splice(this.points.length, 0, ...points); this.points.sort(TimePoint.compare); return this; } /** * 根据上一个时间点计算出下一个合法的时间点 * @param leftBoundary 上一个时间点 * @returns 下一个合法的时间点,下一个时间点和上一个时间点可能时分秒相同但个属于不同的天,也可能属于同一天,但是时分秒不同 */ generateNextTime(leftBoundary) { if (!(typeof leftBoundary === "number")) { leftBoundary = leftBoundary.getTime(); } if (leftBoundary < 1000000000) { leftBoundary = 0; } else if (leftBoundary < 10000000000) { leftBoundary *= 1000; } let nextDate; if (leftBoundary < Date.now()) { nextDate = new Date(); } else { nextDate = new Date(leftBoundary); } let nextPoint; for (const tp of this.points) { nextDate.setHours(tp.hour, tp.minute, tp.second, 0); if (leftBoundary < nextDate.getTime()) { nextPoint = tp; break; } } // 如果没找到下一个 nextPoint,则切换到后一天 if (nextPoint === undefined) { nextDate.setDate(nextDate.getDate() + 1); nextDate.setHours(this.points[0].hour, this.points[0].minute, this.points[0].second, 0); } else { nextDate.setHours(nextPoint.hour, nextPoint.minute, nextPoint.second); } if (this.nextRule != undefined) { return this.nextRule.adjustTime(nextDate, true); } else { return nextDate; } } } /** * 天级时间范围规则,例如周一至周五,周六,周天 */ class DayRangeRule { /** * @param _default 一周7天,每天是否默认,true 表示包括,false 表示不包括 */ constructor(_default) { this.days = Array.from({ length: 7 }, () => _default); } addDays(...days) { for (let day of days) { this.days[day] = true; } } excludeDays(...days) { for (let day of days) { this.days[day] = false; } } /** * 如果需要,调整时间对象 dt,使其落在本规则规定的天内 * @param dt 需要被调整的时间 * @param keepPoint 是否保持之前的时分秒,如果 true,则调整过程不会改变时分秒,只会改变天 * @returns 被调整后的时间,可能等于被调整的时间 */ adjustTime(dt, keepPoint = false) { let day = dt.getDay(); if (this.days[day] === true) { return dt; } // 找到第一个为true的天 for (let i = 1; i < 7; i++) { if (this.days[(day + i) & 7] === true) { dt.setDate(dt.getDate() + i); if (!keepPoint) { dt.setHours(0, 0, 0, 0); } break; } } return dt; } } /** * 小时级时间范围规则,分为所在范围,和排除范围 */ class HourRangeRule { constructor(includeOrExclude) { this.includeOrExclude = includeOrExclude; this.rangeArrays = Array.from({ length: 7 }, () => []); } /** * 添加时间段 * @param range 时间段,由两个 TimePoint 组成 * @param daysOfWeek 时间段所在天 */ addRange(range, ...daysOfWeek) { let days; if (daysOfWeek.length == 0) { days = Array.from({ length: 7 }, (_, i) => i); } else { days = daysOfWeek; } for (const d of days) { this._addRangeToOneDay(range, d); } } /** * 将时间段添加到同属于某天(DayOfWeekNum)的列表 * @param range 时间段,由两个 TimePoint 组成 * @param day 时间段所在的天 */ _addRangeToOneDay(range, day) { let ranges = this.rangeArrays[day]; let resultRanges = []; let newRange = range; let i = 0; // 把 ranges 前面和 newRange 区间没重合(没有交集,且距离大于1)的区间加入到结果集 while (i < ranges.length && TimePoint.subtract(newRange[0], ranges[i][1]) > 1) { resultRanges.push(ranges[i]); i++; } // 合并重合区间 while (i < ranges.length && TimePoint.subtract(ranges[i][0], newRange[1]) < 2) { newRange[0] = TimePoint.subtract(ranges[i][0], newRange[0]) < 0 ? ranges[i][0] : newRange[0]; newRange[1] = TimePoint.subtract(ranges[i][1], newRange[1]) > 0 ? ranges[i][1] : newRange[1]; i++; } resultRanges.push(newRange); // 将剩余区间加入到结果集 while (i < ranges.length) { resultRanges.push(ranges[i]); i++; } this.rangeArrays[day] = resultRanges; } /** * 调整时间 dt,使其落在本规则规定的时间范围内 * @param dt 被调整的时间 * @returns 调整后的时间 */ adjustTime(dt) { let newDt = new Date(dt.getTime()); let d = dt.getDay(); let tp = new TimePoint(dt.getHours(), dt.getMinutes(), dt.getSeconds()); if (this.includeOrExclude) { // 先在当天允许的时间段内检查,如果可以的话就在当天允许的时间段内调整 let tps = this.rangeArrays[d]; let i = 0; while (i < tps.length) { if (TimePoint.compare(tp, tps[i][0]) < 0) { newDt.setHours(tps[i][0].hour, tps[i][0].minute, tps[i][0].second); break; } else if (TimePoint.compare(tp, tps[i][1]) <= 0) { break; } i++; } // 如果遍历完了当天的时间段都没调整好,就得往后找第一个时间段不为空的天 if (i == tps.length) { let j = 1; while (j < 8) { let newD = (d + j) % 7; if (this.rangeArrays[newD].length > 0) { newDt.setDate(newDt.getDate() + j); let _range = this.rangeArrays[newD][0][0]; newDt.setHours(_range.hour, _range.minute, _range.second); break; } } } return newDt; } else { while (true) { let i = 0; while (i < this.rangeArrays[d].length && TimePoint.compare(tp, this.rangeArrays[d][i][1]) > 0) { i++; } if (i == this.rangeArrays[d].length) { break; } if (TimePoint.compare(tp, this.rangeArrays[d][i][0]) < 0) { break; } else { newDt.setHours(this.rangeArrays[d][i][1].hour, this.rangeArrays[d][i][1].minute, this.rangeArrays[d][i][1].second + 1); } if (newDt.getDay() == d) { break; } else { d = newDt.getDay(); } } } return newDt; } } /** * 频率规则,例如没多少天,多少小时,多少分钟,多少秒 */ class Frequency extends Head { /** * * @param count 多少个时间单位 * @param unitDuration 每个时间单位的时长(秒数) */ constructor(count, unitDuration) { super(); this.count = count; this.unitDuration = unitDuration; } /** * 根据上一个时间、本规则规定频率以及当前时间产生下一个时间,但是保证产生的时间为未来时间 * @param lastTime 上一个时间 * @returns 根据上一个时间和本频率规则计算出的下一个时间 */ generateNextTime(lastTime) { if (typeof lastTime === "number") { if (lastTime < 1000000000) { lastTime = 0; } else if (lastTime < 10000000000) { lastTime *= 1000; } } else { lastTime = lastTime.getTime(); } let nextTime = lastTime + this.count * this.unitDuration * 1000; if (nextTime < Date.now()) { nextTime = Date.now() + 1000; } let newDate = new Date(nextTime); if (this.nextRule != undefined) { newDate = this.nextRule.adjustTime(newDate, false); } return newDate; } } class SecondFrequency extends Frequency { constructor(count) { super(count, 1); } } class MinuteFrequency extends Frequency { constructor(count = 1) { super(count, 60); } } class HourFrequency extends Frequency { constructor(count = 1) { super(count, 3600); } } class DayFrequency extends Frequency { constructor(count = 1) { super(count, 86400); } } /** * 规则链 * 内部至少包含一个 HeadRule 对象,该 HeadRule 可选的连接一个 TailRule 对象。 * 未来随着规则越来越复杂,一个规则链内部可以包含三个及以上规则对象。 */ class RuleChain { constructor(headRule, tailRule) { this.headRule = headRule; if (tailRule != undefined) { this.headRule.setNextRule(tailRule); } } setTailRule(rule) { this.headRule.setNextRule(rule); } /** * * @param previousTime 上个时间,毫秒表示的时间戳 * @returns 下一个合法时间点,毫秒表示的时间戳 */ generateNextTime(previousTime) { return this.headRule.generateNextTime(previousTime).getTime(); } } export { Head, RuleChain, TimePointRule, Frequency, SecondFrequency, MinuteFrequency, HourFrequency, DayFrequency, HourRangeRule, DayRangeRule, };