iobroker.javascript
Version:
Javascript/Coffescript Script Engine for ioBroker
326 lines (304 loc) • 11.7 kB
JavaScript
'use strict';
// const DEFAULT = {
// time: {
// exactTime: false,
//
// start: '00:00',
// end: '23:59',
//
// mode: 'hours',
// interval: 1,
// },
// period: {
// once: '',
// days: 1,
// dows: '',
// dates: '',
// weeks: 0,
// months: '',
//
// years: 0,
// yearMonth: 0,
// yearDate: 0,
// },
// valid: {
// from: '',
// to: ''
// }
// };
class Scheduler {
constructor(log) {
this.list = {};
this.log = log || {
debug: function (text) {console.log(text);},
info: function (text) {console.log(text);},
log: function (text) {console.log(text);},
warn: function (text) {console.warn(text);},
error: function (text) {console.error(text);},
silly: function (text) {console.log(text);}
};
}
_getId() {
return Math.round(Math.random() * 1000000) + '.' + Date.now();
}
recalculate() {
const count = Object.keys(this.list).length;
if (count && !this.timer) {
const d = new Date();
d.setMilliseconds(0);
d.setSeconds(0);
d.setMinutes(d.getMinutes() + 1);
this.timer = setTimeout(() => this.checkSchedules(), d.getTime() - Date.now());
} else if (!count && this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
getContext() {
const now = new Date();
return {
now: now.getTime(),
minutesOfDay: now.getHours() * 60 + now.getMinutes(),
y: now.getFullYear(),
M: now.getMonth(),
d: now.getDate(),
h: now.getHours(),
m: now.getMinutes(),
dow: now.getDay()
};
}
checkSchedules() {
const context = this.getContext();
for (const id in this.list) {
if (!this.list.hasOwnProperty(id)) continue;
if (this.checkSchedule(context, this.list[id])) {
typeof this.list[id].cb === 'function' && this.list[id].cb(id);
}
}
const d = new Date();
// if callback last more than a minute
if (d.getTime() - context.now > 60000) {
setImmediate(() => this.checkSchedules());
} else {
d.setMilliseconds(0);
d.setSeconds(0);
d.setMinutes(d.getMinutes() + 1);
this.timer = setTimeout(() => this.checkSchedules(), d.getTime() - Date.now());
}
}
monthDiff(d1, d2) {
let months;
months = (d2.getFullYear() - d1.getFullYear()) * 12;
months -= d1.getMonth() + 1;
months += d2.getMonth();
return months <= 0 ? 0 : months;
}
checkSchedule(context, schedule) {
if (schedule.valid) {
if (schedule.valid.from && !this.isPast(schedule.valid.from) && !this.isToday(schedule.valid.from)) {
return;
}
// "to" date is in the past => delete it from list
if (schedule.valid.to && this.isPast(schedule.valid.to)) {
delete this.list[schedule.id];
return;
}
}
if (schedule.period) {
if (schedule.period.once && !this.isToday(schedule.period.once)) {
if (this.isPast(schedule.period.once)) {
delete this.list[schedule.id];
}
return;
} else if (schedule.period.days) {
if (schedule.period.dows && schedule.period.dows.indexOf(context.dow) === -1) {
return;
} else
if (schedule.period.days > 1) {
const diff = Math.round((context.now - schedule.valid.fromDate) / (60000 * 24) + 0.5);
if (diff % schedule.period.days) {
return;
}
}
} else if (schedule.period.weeks) {
if (schedule.period.dows && schedule.period.dows.indexOf(context.dow) === -1) {
return;
}
if (schedule.period.weeks > 1) {
const diff = Math.round((context.now - schedule.valid.fromDate) / (60000 * 24 * 7) + 0.5);
if (diff % schedule.period.days) {
return;
}
}
} else if (schedule.period.months) {
if (schedule.period.months && schedule.period.months.indexOf(context.M) === -1) {
return;
}
if (typeof schedule.period.months === 'number' && schedule.period.months > 1) {
const diff = this.monthDiff(schedule.period.fromDate, new Date(context.now));
if (diff % schedule.period.months) {
return;
}
}
} else if (schedule.period.years) {
if (schedule.period.years.yearMonth && schedule.period.years.yearMonth !== context.M) {
return;
}
if (schedule.period.years.yearDate && schedule.period.years.yearDate !== context.d) {
return;
}
if (schedule.period.years > 1) {
const diff = Math.floor(this.monthDiff(schedule.period.fromDate, new Date(context.now)) / 12);
if (diff % schedule.period.years) {
return;
}
}
}
}
if (schedule.time) {
if (schedule.time.exactTime) {
if (context.minutesOfDay !== schedule.time.start) {
return;
}
} else {
if (schedule.time.start > context.minutesOfDay || (schedule.time.end && schedule.time.end < context.minutesOfDay)) {
return;
}
if (schedule.time.mode === 60) {
if (schedule.time.interval > 1 && ((context.minutesOfDay - schedule.time.start) % schedule.time.interval)) {
return;
}
} else
if (schedule.time.mode === 3600) {
if ((context.minutesOfDay - schedule.time.start) % (schedule.time.interval * 60)) {
return;
}
}
}
}
return true;
}
string2date(date) {
let parts = date.split('.');
let d;
if (parts.length !== 3) {
parts = date.split('-');
d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
} else {
d = new Date(parseInt(parts[2], 10), parseInt(parts[1], 10) - 1, parseInt(parts[0], 10));
}
return {y: d.getFullYear(), M: d.getMonth(), d: d.getDate()};
}
isPast(context, date) {
if (date) {
if (date.y < context.y) {
return true;
} else if (date.y === context.y) {
if (date.M < context.M) {
return true;
} else if (date.M === context.M) {
if (date.d < context.d) {
return true;
}
}
}
}
}
isToday(context, date) {
return date && date.y === context.y && date.M === context.M && date.d === context.d;
}
add(schedule, cb) {
if (typeof schedule === 'string') {
try {
schedule = JSON.parse(schedule);
} catch (e) {
this.log.error('Cannot parse schedule: ' + schedule);
return;
}
}
const id = this._getId();
if (typeof schedule !== 'object' || !schedule.period) {
return this.log.error('Invalid schedule structure: ' + JSON.stringify(schedule));
}
const context = this.getContext();
const sch = JSON.parse(JSON.stringify(schedule));
sch.id = id;
sch.cb = cb;
if (sch.time && sch.time.start) {
const parts = sch.time.start.split(':');
sch.time.start = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
}
if (sch.time && sch.time.end) {
const parts = sch.time.end.split(':');
sch.time.end = parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
}
if (sch.time.mode === 'minutes') {
sch.time.mode = 60;
} else if (sch.time.mode === 'hours') {
sch.time.mode = 3600;
}
sch.period.once = sch.period.once && this.string2date(sch.period.once);
if (sch.valid) {
sch.valid.from = sch.valid.from && this.string2date(sch.valid.from);
sch.valid.to = sch.valid.to && this.string2date(sch.valid.to);
if (this.isPast(context, sch.valid.to)) {
this.log.warn('End of schedule is in the past');
return;
}
if ((typeof sch.period.days === 'number' && sch.period.days > 1)) {
// fromDate must be unix time
sch.valid.fromDate = new Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d).getTime();
} else if (typeof sch.period.weeks === 'number' && sch.period.weeks > 1) {
sch.valid.fromDate = sch.valid.from ? new Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d) : new Date();
//sch.valid.fromDate.setDate(-sch.valid.fromDate.getDate() - sch.valid.fromDate.getDay());
// fromDate must be unix time
sch.valid.fromDate = sch.valid.fromDate.getTime();
} else if (typeof sch.period.months === 'number' && sch.period.months > 1) {
// fromDate must be object
sch.valid.fromDate = new Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d);
} else if (typeof sch.period.years === 'number' && sch.period.years > 1) {
// fromDate must be object
sch.valid.fromDate = new Date(sch.valid.from.y, sch.valid.from.M, sch.valid.from.d);
}
}
if (sch.period.dows) {
try {
sch.period.dows = JSON.parse(sch.period.dows);
} catch (e) {
this.log.error('Cannot parse day of weeks: ' + sch.period.dows);
return;
}
}
if (sch.period.months) {
try {
sch.period.months = JSON.parse(sch.period.months);
} catch (e) {
this.log.error('Cannot parse day of months: ' + sch.period.months);
return;
}
sch.period.months = sch.period.months.map(m => m - 1);
}
if (sch.period.dates) {
try {
sch.period.dates = JSON.parse(sch.period.dates);
} catch (e) {
this.log.error('Cannot parse day of dates: ' + sch.period.dates);
return;
}
}
sch.period.yearMonth = sch.period.yearMonth && (sch.period.yearMonth - 1);
this.list[id] = sch;
this.recalculate();
return id;
}
remove(id) {
if (this.list[id]) {
delete this.list[id];
this.recalculate();
}
}
get(id) {
return typeof id === 'string' && this.list[id];
}
}
module.exports = Scheduler;