UNPKG

occaecatidicta

Version:
380 lines (319 loc) 10.9 kB
/** * This is the trigger used to decode the cronTimer and calculate the next excution time of the cron Trigger. */ import { Job } from './job'; import { getLogger } from 'omelox-logger'; import * as path from 'path'; let logger = getLogger('omelox-scheduler', path.basename(__filename)); let SECOND = 0; let MIN = 1; let HOUR = 2; let DOM = 3; let MONTH = 4; let DOW = 5; let Limit = [ [0, 59], [0, 59], [0, 24], [1, 31], [0, 11], [0, 6] ]; export class CronTrigger { trigger: any; nextTime: number; job: Job; /** * The constructor of the CronTrigger * @param trigger The trigger str used to build the cronTrigger instance */ constructor(trigger: string, job: Job) { this.trigger = this.decodeTrigger(trigger); this.nextTime = this.nextExcuteTime(Date.now()); this.job = job; } /** * Get the current excuteTime of trigger */ excuteTime() { return this.nextTime; } /** * Caculate the next valid cronTime after the given time * @param The given time point * @return The nearest valid time after the given time point */ nextExcuteTime(time: number) { // add 1s to the time so it must be the next time time = !!time ? time : this.nextTime; time += 1000; let cronTrigger = this.trigger; let date = new Date(time); date.setMilliseconds(0); outmost: while (true) { if (date.getFullYear() > 2999) { logger.error('Can\'t compute the next time, exceed the limit'); return null; } if (!timeMatch(date.getMonth(), cronTrigger[MONTH])) { let nextMonth = nextCronTime(date.getMonth(), cronTrigger[MONTH]); if (nextMonth == null) return null; if (nextMonth <= date.getMonth()) { date.setFullYear(date.getFullYear() + 1); date.setMonth(0); date.setDate(1); date.setHours(0); date.setMinutes(0); date.setSeconds(0); continue; } date.setDate(1); date.setMonth(nextMonth); date.setHours(0); date.setMinutes(0); date.setSeconds(0); } if (!timeMatch(date.getDate(), cronTrigger[DOM]) || !timeMatch(date.getDay(), cronTrigger[DOW])) { let domLimit = getDomLimit(date.getFullYear(), date.getMonth()); do { let nextDom = nextCronTime(date.getDate(), cronTrigger[DOM]); if (nextDom == null) return null; // If the date is in the next month, add month if (nextDom <= date.getDate() || nextDom > domLimit) { date.setDate(1); date.setMonth(date.getMonth() + 1); date.setHours(0); date.setMinutes(0); date.setSeconds(0); continue outmost; } date.setDate(nextDom); } while (!timeMatch(date.getDay(), cronTrigger[DOW])); date.setHours(0); date.setMinutes(0); date.setSeconds(0); } if (!timeMatch(date.getHours(), cronTrigger[HOUR])) { let nextHour = nextCronTime(date.getHours(), cronTrigger[HOUR]); if (nextHour <= date.getHours()) { date.setDate(date.getDate() + 1); date.setHours(nextHour); date.setMinutes(0); date.setSeconds(0); continue; } date.setHours(nextHour); date.setMinutes(0); date.setSeconds(0); } if (!timeMatch(date.getMinutes(), cronTrigger[MIN])) { let nextMinute = nextCronTime(date.getMinutes(), cronTrigger[MIN]); if (nextMinute <= date.getMinutes()) { date.setHours(date.getHours() + 1); date.setMinutes(nextMinute); date.setSeconds(0); continue; } date.setMinutes(nextMinute); date.setSeconds(0); } if (!timeMatch(date.getSeconds(), cronTrigger[SECOND])) { let nextSecond = nextCronTime(date.getSeconds(), cronTrigger[SECOND]); if (nextSecond <= date.getSeconds()) { date.setMinutes(date.getMinutes() + 1); date.setSeconds(nextSecond); continue; } date.setSeconds(nextSecond); } break; } this.nextTime = date.getTime(); return this.nextTime; } /** * Decude the cronTrigger string to arrays * @param cronTimeStr The cronTimeStr need to decode, like "0 12 * * * 3" * @return The array to represent the cronTimer */ decodeTrigger(cronTimeStr: string) { cronTimeStr = cronTimeStr.trim(); let cronTimes = <any[]>cronTimeStr.split(/\s+/); if (cronTimes.length !== 6) { console.log('error'); return null; } for (let i = 0; i < cronTimes.length; i++) { cronTimes[i] = (this.decodeTimeStr(cronTimes[i], i)); if (!checkNum(cronTimes[i], Limit[i][0], Limit[i][1])) { logger.error('Decode crontime error, value exceed limit!' + JSON.stringify({ cronTime: cronTimes[i], limit: Limit[i] })); return null; } } return cronTimes; } /** * Decode the cron Time string * @param timeStr The cron time string, like: 1,2 or 1-3 * @return A sorted array, like [1,2,3] */ decodeTimeStr(timeStr: any, type: number) { let result: {[key: number]: number} = {}; let arr = []; if (timeStr === '*') { return -1; } else if (timeStr.search(',') > 0) { let timeArr = timeStr.split(','); for (let i = 0; i < timeArr.length; i++) { let time: any = timeArr[i]; if (time.match(/^\d+-\d+$/)) { decodeRangeTime(result, time); } else if (time.match(/^\d+\/\d+/)) { decodePeriodTime(result, time, type); } else if (!isNaN(time)) { let num = Number(time); result[num] = num; } else return null; } } else if (timeStr.match(/^\d+-\d+$/)) { decodeRangeTime(result, timeStr); } else if (timeStr.match(/^\d+\/\d+/)) { decodePeriodTime(result, timeStr, type); } else if (!isNaN(timeStr)) { let num = Number(timeStr); result[num] = num; } else { return null; } for (let key in result) { arr.push(result[key]); } arr.sort(function (a, b) { return a - b; }); return arr; } } /** * return the next match time of the given value * @param value The time value * @param cronTime The cronTime need to match * @return The match value or null if unmatch(it offten means an error occur). */ function nextCronTime(value: number, cronTime: Array<number>) { value += 1; if (typeof (cronTime) === 'number') { if (cronTime === -1) return value; else return cronTime; } else if (typeof (cronTime) === 'object' && cronTime instanceof Array) { if (value <= cronTime[0] || value > cronTime[cronTime.length - 1]) return cronTime[0]; for (let i = 0; i < cronTime.length; i++) if (value <= cronTime[i]) return cronTime[i]; } logger.warn('Compute next Time error! value :' + value + ' cronTime : ' + cronTime); return null; } /** * Match the given value to the cronTime * @param value The given value * @param cronTime The cronTime * @return The match result */ function timeMatch(value: number, cronTime: Array<number>) { if (typeof (cronTime) === 'number') { if (cronTime === -1) return true; if (value === cronTime) return true; return false; } else if (typeof (cronTime) === 'object' && cronTime instanceof Array) { if (value < cronTime[0] || value > cronTime[cronTime.length - 1]) return false; for (let i = 0; i < cronTime.length; i++) if (value === cronTime[i]) return true; return false; } return null; } /** * Decode time range * @param map The decode map * @param timeStr The range string, like 2-5 */ function decodeRangeTime(map: {[key: number]: number}, timeStr: string): void { let times = <any[]>timeStr.split('-'); times[0] = Number(times[0]); times[1] = Number(times[1]); if (times[0] > times[1]) { console.log('Error time range'); return null; } for (let i = times[0]; i <= times[1]; i++) { map[i] = i; } } /** * Compute the period timer */ function decodePeriodTime(map: {[key: number]: number}, timeStr: string, type: number) { let times = timeStr.split('/'); let min = Limit[type][0]; let max = Limit[type][1]; let remind = Number(times[0]); let period = Number(times[1]); if (period === 0) return; for (let i = remind; i <= max; i += period) { // if (i % period == remind) map[i] = i; // } } } /** * Check if the numbers are valid * @param nums The numbers array need to check * @param min Minimus value * @param max Maximam value * @return If all the numbers are in the data range */ function checkNum(nums: any, min: number, max: number) { if (nums == null) return false; if (nums === -1) return true; for (let i = 0; i < nums.length; i++) { if (nums[i] < min || nums[i] > max) return false; } return true; } /** * Get the date limit of given month * @param The given year * @month The given month * @return The date count of given month */ function getDomLimit(year: number, month: number) { let date = new Date(year, month + 1, 0); return date.getDate(); } /** * Create cronTrigger * @param trigger The Cron Trigger string * @return The Cron trigger */ export function createTrigger(trigger: string, job: Job) { return new CronTrigger(trigger, job); }