timed-silky
Version:
Timed-Silky 是基于一款基于 TypeScript 的定时任务调度器。得益于 TypeScript,Timed-Silky 可以用符合特定句式的自然语言描述任务的调度规则,提供丝滑的链式调用接口。
354 lines (353 loc) • 11.8 kB
JavaScript
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, };