node-red-contrib-chronos
Version:
Time-based Node-RED scheduling, repeating, queueing, routing, filtering and manipulating nodes
1,000 lines (912 loc) • 40.6 kB
JavaScript
/*
* Copyright (c) 2020 - 2025 Jens-Uwe Rossbach
*
* This code is licensed under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
function validateCondition(node, cond)
{
if ((cond.operator == "context") && !cond.context.value)
{
return false;
}
if ((cond.operator == "equal") ||
(cond.operator == "notEqual") ||
(cond.operator == "before") ||
(cond.operator == "until") ||
(cond.operator == "since") ||
(cond.operator == "after"))
{
if ((cond.operands.type == "time") && !node.chronos.isValidUserTime(cond.operands.value, false))
{
return false;
}
}
if ((cond.operator == "between") || (cond.operator == "outside"))
{
if ((cond.operands[0].type == "time") && !node.chronos.isValidUserTime(cond.operands[0].value, false))
{
return false;
}
if ((cond.operands[1].type == "time") && !node.chronos.isValidUserTime(cond.operands[1].value, false))
{
return false;
}
}
return true;
}
function convertCondition(RED, node, cond, num)
{
if ((typeof cond != "object") || !cond)
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Condition: Not an object or null"});
}
if (!/^(equal|notEqual|before|until|since|after|between|outside|days|weekdays|months|otherwise)$/.test(cond.operator))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operator: Invalid value", value: cond.operator});
}
if ((cond.operator == "equal") ||
(cond.operator == "notEqual") ||
(cond.operator == "before") ||
(cond.operator == "until") ||
(cond.operator == "since") ||
(cond.operator == "after"))
{
validateOperand(RED, node, cond.operands, num);
}
if ((cond.operator == "between") || (cond.operator == "outside"))
{
if (!cond.operands || !Array.isArray(cond.operands) || (cond.operands.length != 2))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Not an array, invalid number of elements or null"});
}
validateOperand(RED, node, cond.operands[0], num);
validateOperand(RED, node, cond.operands[1], num);
}
if (cond.operator == "days")
{
if ((typeof cond.operands != "object") || !cond.operands)
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Not an object or null"});
}
if (!/^(first|second|third|fourth|fifth|last|even|specific)$/.test(cond.operands.type))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid type", value: cond.operands.type});
}
if (((cond.operands.type == "first") || (cond.operands.type == "last")) &&
!/^(monday|tuesday|wednesday|thursday|friday|saturday|sunday|day|workday|weekend)$/.test(cond.operands.day))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid day", value: cond.operands.day});
}
if (((cond.operands.type == "second") || (cond.operands.type == "third") || (cond.operands.type == "fourth") || (cond.operands.type == "fifth")) &&
!/^(monday|tuesday|wednesday|thursday|friday|saturday|sunday|day)$/.test(cond.operands.day))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid day", value: cond.operands.day});
}
if ((cond.operands.type == "specific")
&& ((typeof cond.operands.day != "number") || (cond.operands.day < 1) || (cond.operands.day > 31)
|| ((typeof cond.operands.month != "undefined") && (typeof cond.operands.month != "number")
&& !/^(january|february|march|april|may|june|july|august|september|october|november|december)$/.test(cond.operands.month))))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid day or month"});
}
if (typeof cond.operands.exclude != "boolean")
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid exclude", value: cond.operands.exclude});
}
}
if (cond.operator == "weekdays")
{
if ((typeof cond.operands != "object") || !cond.operands)
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Not an object or null"});
}
if ((("sunday" in cond.operands) && (typeof cond.operands.sunday != "boolean")) ||
(("monday" in cond.operands) && (typeof cond.operands.monday != "boolean")) ||
(("tuesday" in cond.operands) && (typeof cond.operands.tuesday != "boolean")) ||
(("wednesday" in cond.operands) && (typeof cond.operands.wednesday != "boolean")) ||
(("thursday" in cond.operands) && (typeof cond.operands.thursday != "boolean")) ||
(("friday" in cond.operands) && (typeof cond.operands.friday != "boolean")) ||
(("saturday" in cond.operands) && (typeof cond.operands.saturday != "boolean")))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid weekday property", value: cond.operands});
}
}
if (cond.operator == "months")
{
if ((typeof cond.operands != "object") || !cond.operands)
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Not an object or null"});
}
if ((("january" in cond.operands) && (typeof cond.operands.january != "boolean")) ||
(("february" in cond.operands) && (typeof cond.operands.february != "boolean")) ||
(("march" in cond.operands) && (typeof cond.operands.march != "boolean")) ||
(("april" in cond.operands) && (typeof cond.operands.april != "boolean")) ||
(("may" in cond.operands) && (typeof cond.operands.may != "boolean")) ||
(("june" in cond.operands) && (typeof cond.operands.june != "boolean")) ||
(("july" in cond.operands) && (typeof cond.operands.july != "boolean")) ||
(("august" in cond.operands) && (typeof cond.operands.august != "boolean")) ||
(("september" in cond.operands) && (typeof cond.operands.september != "boolean")) ||
(("october" in cond.operands) && (typeof cond.operands.october != "boolean")) ||
(("november" in cond.operands) && (typeof cond.operands.november != "boolean")) ||
(("december" in cond.operands) && (typeof cond.operands.december != "boolean")))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid month property", value: cond.operands});
}
}
const convCond = {operator: cond.operator};
if ((convCond.operator == "equal") ||
(convCond.operator == "notEqual") ||
(convCond.operator == "before") ||
(convCond.operator == "until") ||
(convCond.operator == "since") ||
(convCond.operator == "after") ||
(convCond.operator == "between") ||
(convCond.operator == "outside"))
{
convCond.operands = cond.operands;
}
else if (convCond.operator == "days")
{
convCond.operands = {};
convCond.operands.type = cond.operands.type;
convCond.operands.exclude = cond.operands.exclude;
if (cond.operands.type != "even")
{
convCond.operands.day = cond.operands.day;
}
if (cond.operands.type == "specific")
{
convCond.operands.month = cond.operands.month ? cond.operands.month : "any";
}
}
else if (convCond.operator == "weekdays")
{
convCond.operands = [false, false, false, false, false, false, false];
convCond.operands[0] = (cond.operands.sunday === true);
convCond.operands[1] = (cond.operands.monday === true);
convCond.operands[2] = (cond.operands.tuesday === true);
convCond.operands[3] = (cond.operands.wednesday === true);
convCond.operands[4] = (cond.operands.thursday === true);
convCond.operands[5] = (cond.operands.friday === true);
convCond.operands[6] = (cond.operands.saturday === true);
}
else if (convCond.operator == "months")
{
convCond.operands = [false, false, false, false, false, false, false, false, false, false, false, false];
convCond.operands[0] = (cond.operands.january === true);
convCond.operands[1] = (cond.operands.february === true);
convCond.operands[2] = (cond.operands.march === true);
convCond.operands[3] = (cond.operands.april === true);
convCond.operands[4] = (cond.operands.may === true);
convCond.operands[5] = (cond.operands.june === true);
convCond.operands[6] = (cond.operands.july === true);
convCond.operands[7] = (cond.operands.august === true);
convCond.operands[8] = (cond.operands.september === true);
convCond.operands[9] = (cond.operands.october === true);
convCond.operands[10] = (cond.operands.november === true);
convCond.operands[11] = (cond.operands.december === true);
}
return convCond;
}
function validateOperand(RED, node, operand, num)
{
if ((typeof operand != "object") || !operand)
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Not an object or null"});
}
if (!/^(time|sun|moon|custom)$/.test(operand.type))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid type", value: operand.type});
}
if ((typeof operand.value != "string")
|| ((operand.type == "time") && !node.chronos.isValidUserTime(operand.value, false))
|| ((operand.type == "sun") && !node.chronos.PATTERN_SUNTIME.test(operand.value))
|| ((operand.type == "moon") && !node.chronos.PATTERN_MOONTIME.test(operand.value)))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid value", value: operand.value});
}
if (!node.chronos.validateOffset(operand))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid offset", value: operand});
}
if (((typeof operand.precision != "undefined") && (typeof operand.precision != "string")) ||
((typeof operand.precision == "string") && !/^(millisecond|second|minute|hour|day|month|year)$/.test(operand.precision)))
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.invalidCondition"),
{condition: num, error: "Operand: Invalid precision", value: operand.precision});
}
}
async function evaluateCondition(RED, node, msg, baseTime, cond, id)
{
let result = false;
if (cond.operator == "expression")
{
let expression = null;
try
{
expression = node.chronos.getJSONataExpression(RED, node, cond.expression);
// time switch/filter node specific JSONata extensions
expression.assign("baseTime", baseTime.valueOf());
expression.registerFunction("isSame", (ts, type, value, offset, random, precision) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "equal",
operands: {
type: type,
value: value,
offset: (typeof offset != "undefined") ? offset : 0,
random: (typeof random != "undefined") ? random : 0,
precision: precision}},
id),
id);
}, "<(sn)ssn?(nb)?s?:b>");
expression.registerFunction("isBefore", (ts, type, value, offset, random, precision) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "before",
operands: {
type: type,
value: value,
offset: (typeof offset != "undefined") ? offset : 0,
random: (typeof random != "undefined") ? random : 0,
precision: precision}},
id),
id);
}, "<(sn)ssn?(nb)?s?:b>");
expression.registerFunction("isAfter", async(ts, type, value, offset, random, precision) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "after",
operands: {
type: type,
value: value,
offset: (typeof offset != "undefined") ? offset : 0,
random: (typeof random != "undefined") ? random : 0,
precision: precision}},
id),
id);
}, "<(sn)ssn?(nb)?s?:b>");
expression.registerFunction("isBetween", (ts, type1, value1, offset1, random1, precision1, type2, value2, offset2, random2, precision2) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "between",
operands: [{
type: type1,
value: value1,
offset: offset1,
random: random1,
precision: precision1}, {
type: type2,
value: value2,
offset: offset2,
random: random2,
precision: precision2}]},
id),
id);
}, "<(sn)ssn(nb)sssn(nb)s:b>");
expression.registerFunction("isOutside", (ts, type1, value1, offset1, random1, precision1, type2, value2, offset2, random2, precision2) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "outside",
operands: [{
type: type1,
value: value1,
offset: offset1,
random: random1,
precision: precision1}, {
type: type2,
value: value2,
offset: offset2,
random: random2,
precision: precision2}]},
id),
id);
}, "<(sn)ssn(nb)sssn(nb)s:b>");
expression.registerFunction("isFirstDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "first",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isSecondDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "second",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isThirdDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "third",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isFourthDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "fourth",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isFifthDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "fifth",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isLastDay", (ts, day) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "last",
day: day ? day : "day",
exclude: false}},
id),
id);
}, "<(sn)s?:b>");
expression.registerFunction("isEvenDay", (ts) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "even",
exclude: false}},
id),
id);
}, "<(sn):b>");
expression.registerFunction("isSpecificDay", (ts, day, month) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "days",
operands: {
type: "specific",
day: day,
month: month,
exclude: false}},
id),
id);
}, "<(sn)n(sn)?:b>");
expression.registerFunction("matchesWeekdays", (ts, days) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "weekdays",
operands: days},
id),
id);
}, "<(sn)o:b>");
expression.registerFunction("matchesMonths", (ts, months) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node, {
operator: "months",
operands: months},
id),
id);
}, "<(sn)o:b>");
expression.registerFunction("evaluateCondition", (ts, condition) =>
{
return evalCondition(
RED,
node,
msg,
node.chronos.getTimeFrom(node, ts),
convertCondition(
RED,
node,
condition,
id),
id);
}, "<(sn)o:b>");
result = await node.chronos.evaluateJSONataExpression(RED, expression, msg);
}
catch (e)
{
if (e instanceof node.chronos.TimeError)
{
throw e;
}
else
{
const details = {condition: id, expression: cond.expression, code: e.code, description: e.message, position: e.position, token: e.token};
if (e.value)
{
details.value = e.value;
}
throw new node.chronos.TimeError(RED._("node-red-contrib-chronos/chronos-config:common.error.evaluationFailed"), details);
}
}
if (typeof result != "boolean")
{
throw new node.chronos.TimeError(
RED._("node-red-contrib-chronos/chronos-config:common.error.notBoolean"),
{condition: id, expression: cond.expression, result: result});
}
}
else
{
result = evalCondition(RED, node, msg, baseTime, cond, id);
}
return result;
}
function evalCondition(RED, node, msg, baseTime, cond, id)
{
let result = false;
if (cond.operator == "context")
{
let ctxData = undefined;
if (cond.context.type == "env")
{
if (typeof cond.context.value == "string")
{
ctxData = RED.util.evaluateNodeProperty(
cond.context.value,
cond.context.type,
node);
}
else
{
ctxData = cond.context.value;
}
}
else
{
const ctx = RED.util.parseContextStore(cond.context.value);
ctxData = node.context()[cond.context.type].get(ctx.key, ctx.store);
}
result = evalCondition(
RED,
node,
msg,
baseTime,
convertCondition(
RED,
node,
ctxData, id),
id);
}
else if (
(cond.operator == "equal") ||
(cond.operator == "notEqual") ||
(cond.operator == "before") ||
(cond.operator == "until") ||
(cond.operator == "since") ||
(cond.operator == "after"))
{
const precision = (cond.operands.precision == "millisecond") ? undefined : cond.operands.precision;
const targetTime = node.chronos.retrieveTime(RED, node, msg, baseTime.clone(), cond.operands.type, cond.operands.value);
targetTime.add(node.chronos.getRandomizedOffset(cond.operands.offset, cond.operands.random), "minutes");
switch (cond.operator)
{
case "equal":
{
result = baseTime.isSame(targetTime, precision);
break;
}
case "notEqual":
{
result = !baseTime.isSame(targetTime, precision);
break;
}
case "before":
{
result = baseTime.isBefore(targetTime, precision);
break;
}
case "until":
{
result = baseTime.isSameOrBefore(targetTime, precision);
break;
}
case "since":
{
result = baseTime.isSameOrAfter(targetTime, precision);
break;
}
case "after":
{
result = baseTime.isAfter(targetTime, precision);
break;
}
}
}
else if ((cond.operator == "between") || (cond.operator == "outside"))
{
const precision1 = (cond.operands[0].precision == "millisecond") ? undefined : cond.operands[0].precision;
const precision2 = (cond.operands[1].precision == "millisecond") ? undefined : cond.operands[1].precision;
const time1 = node.chronos.retrieveTime(RED, node, msg, baseTime.clone(), cond.operands[0].type, cond.operands[0].value);
time1.add(node.chronos.getRandomizedOffset(cond.operands[0].offset, cond.operands[0].random), "minutes");
const time2 = node.chronos.retrieveTime(RED, node, msg, baseTime.clone(), cond.operands[1].type, cond.operands[1].value);
time2.add(node.chronos.getRandomizedOffset(cond.operands[1].offset, cond.operands[1].random), "minutes");
if (time1.isSame(time2, "day"))
{
if (time1.isBefore(time2))
{
result = (
((cond.operator == "between") && (baseTime.isSameOrAfter(time1, precision1) && baseTime.isSameOrBefore(time2, precision2))) ||
((cond.operator == "outside") && (baseTime.isBefore(time1, precision1) || baseTime.isAfter(time2, precision2))));
}
else
{
result = (
((cond.operator == "between") && (baseTime.isSameOrAfter(time1, precision1) || baseTime.isSameOrBefore(time2, precision2))) ||
((cond.operator == "outside") && (baseTime.isBefore(time1, precision1) && baseTime.isAfter(time2, precision2))));
}
}
else if (time1.isSameOrBefore(time2))
{
result = (
((cond.operator == "between") && (baseTime.isSameOrAfter(time1, precision1) && baseTime.isSameOrBefore(time2, precision2))) ||
((cond.operator == "outside") && (baseTime.isBefore(time1, precision1) || baseTime.isAfter(time2, precision2))));
}
else
{
result = (
((cond.operator == "between") && (baseTime.isSameOrAfter(time2, precision2) && baseTime.isSameOrBefore(time1, precision1))) ||
((cond.operator == "outside") && (baseTime.isBefore(time2, precision2) || baseTime.isAfter(time1, precision1))));
}
}
else
{
result = evaluateDateCondition(baseTime, cond);
}
return result;
}
function evaluateDateCondition(baseTime, cond)
{
let result = false;
if ((cond.operator == "days"))
{
if (cond.operands.type == "specific")
{
const MONTHS = {january: 0, february: 1, march: 2, april: 3, may: 4, june: 5, july: 6, august: 7, september: 8, october: 9, november: 10, december: 11};
result = ((baseTime.date() == cond.operands.day) && ((cond.operands.month == "any") || (baseTime.month() == ((typeof cond.operands.month == "string") ? MONTHS[cond.operands.month] : cond.operands.month - 1))));
}
else if (cond.operands.type == "even")
{
result = ((baseTime.date() % 2) == 0);
}
else
{
const WEEKDAYS = {sunday: 0, monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6};
if (cond.operands.type == "last")
{
const lastDay = baseTime.clone().endOf("month");
switch (cond.operands.day)
{
case "sunday":
case "monday":
case "tuesday":
case "wednesday":
case "thursday":
case "friday":
case "saturday":
{
let diff = lastDay.day() - WEEKDAYS[cond.operands.day];
if (diff < 0)
{
diff += 7;
}
lastDay.subtract(diff, "days");
result = (baseTime.date() == lastDay.date());
break;
}
case "day":
{
result = (baseTime.date() == lastDay.date());
break;
}
case "workday":
{
if (lastDay.day() == 6) // Saturday
{
lastDay.subtract(1, "days");
}
else if (lastDay.day() == 0) // Sunday
{
lastDay.subtract(2, "days");
}
result = (baseTime.date() == lastDay.date());
break;
}
case "weekend":
{
const day = lastDay.day();
if ((day >= 1) && (day <= 5)) // Monday .. Friday
{
lastDay.subtract(day, "days");
}
result = (baseTime.date() == lastDay.date());
break;
}
}
}
else
{
const ORDINALS = {first: 0, second: 1, third: 2, fourth: 3, fifth: 4};
const firstDay = baseTime.clone().startOf("month");
switch (cond.operands.day)
{
case "sunday":
case "monday":
case "tuesday":
case "wednesday":
case "thursday":
case "friday":
case "saturday":
{
let diff = WEEKDAYS[cond.operands.day] - firstDay.day();
if (diff < 0)
{
diff += 7;
}
firstDay.add(diff + (7 * ORDINALS[cond.operands.type]), "days");
result = ((baseTime.date() == firstDay.date()) && (baseTime.month() == firstDay.month()));
break;
}
case "day":
{
result = (baseTime.date() == (ORDINALS[cond.operands.type] + 1));
break;
}
case "workday":
{
if (firstDay.day() == 6) // Saturday
{
firstDay.add(2, "days");
}
else if (firstDay.day() == 0) // Sunday
{
firstDay.add(1, "days");
}
result = (baseTime.date() == firstDay.date());
break;
}
case "weekend":
{
const day = firstDay.day();
if ((day >= 1) && (day <= 5)) // Monday .. Friday
{
firstDay.add(6 - day, "days");
}
result = (baseTime.date() == firstDay.date());
break;
}
}
}
}
if (cond.operands.exclude)
{
result = !result;
}
}
else if ((cond.operator == "weekdays"))
{
result = cond.operands[baseTime.day()];
}
else if ((cond.operator == "months"))
{
result = cond.operands[baseTime.month()];
}
return result;
}
function getBaseTime(RED, node, msg)
{
let ret = null;
if (node.baseTimeType == "msgIngress")
{
ret = node.chronos.getCurrentTime(node);
}
else
{
let value = null;
switch (node.baseTimeType)
{
case "global":
case "flow":
{
const ctx = RED.util.parseContextStore(node.baseTime);
value = node.context()[node.baseTimeType].get(ctx.key, ctx.store);
break;
}
case "msg":
{
value = RED.util.getMessageProperty(msg, node.baseTime);
break;
}
}
if ((typeof value == "number") || (typeof value == "string"))
{
ret = node.chronos.getTimeFrom(node, value);
}
}
return ret ? (ret.isValid() ? ret : null) : null;
}
module.exports =
{
validateCondition: validateCondition,
convertCondition: convertCondition,
evaluateCondition: evaluateCondition,
evaluateDateCondition: evaluateDateCondition,
getBaseTime: getBaseTime
};