├── .gitignore
├── .node-version
├── README.md
├── icons
└── switch.png
├── index.html
├── index.js
├── package-lock.json
├── package.json
└── tests
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
29 | node_modules
30 |
31 | # Created by .ignore support plugin (hsz.mobi)
32 |
33 | .idea
34 | .history
35 | .vscode
36 | .nyc_output
37 | coverage
38 |
39 | *.tgz
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 12.21.0
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Time Range Switch
2 |
3 | A simple Node-RED node that routes messages depending on the time. If the current time falls within the range specified
4 | in the node configuration, the message is routed to output 1. Otherwise the message is routed to output 2.
5 |
6 |
7 | ### Installation
8 |
9 | Change directory to your node red installation:
10 |
11 | $ npm install node-red-contrib-time-range-switch
12 |
13 | ### Configuration
14 |
15 | The times can be a 24 hour time or a [suncalc](https://github.com/mourner/suncalc) event:
16 |
17 |
18 | | Time | Description |
19 | | --------------- | ------------------------------------------------------------------------ |
20 | | `00:00 ... 23:59` | 24hr time in hours and minutes |
21 | | `00:00:03 ... 23:59:13` | 24hr time in hours minutes and seconds |
22 | | `sunrise` | sunrise (top edge of the sun appears on the horizon) |
23 | | `sunriseEnd` | sunrise ends (bottom edge of the sun touches the horizon) |
24 | | `goldenHourEnd` | morning golden hour (soft light, best time for photography) ends |
25 | | `solarNoon` | solar noon (sun is in the highest position) |
26 | | `goldenHour` | evening golden hour starts |
27 | | `sunsetStart` | sunset starts (bottom edge of the sun touches the horizon) |
28 | | `sunset` | sunset (sun disappears below the horizon, evening civil twilight starts) |
29 | | `dusk` | dusk (evening nautical twilight starts) |
30 | | `nauticalDusk` | nautical dusk (evening astronomical twilight starts) |
31 | | `night` | night starts (dark enough for astronomical observations) |
32 | | `nadir` | nadir (darkest moment of the night, sun is in the lowest position) |
33 | | `nightEnd` | night ends (morning astronomical twilight starts) |
34 | | `nauticalDawn` | nautical dawn (morning nautical twilight starts) |
35 | | `dawn` | dawn (morning nautical twilight ends, morning civil twilight starts) |
36 |
37 | ### Offsets
38 |
39 |
40 | The start and end time can have an offset. This is specified in minutes:
41 | - -ve number brings the time forward. E.g. if the time is dusk and offset is -60, the start time will be 60 minutes before dusk.
42 | - +ve number delays the time by the specified number of minutes
43 |
44 | ### Programmatic Configuration
45 |
46 | This node can be controlled programmatically by sending configuration settings to the node input.
47 |
48 | **It is very important to note that properties set programmatically in this manner are transient. They will not persist over a NodeRED restart or redeploy!**
49 |
50 | E.g. send the following to the input:
51 | ```javascript
52 | msg.__config = {
53 | startTime: '12:35',
54 | endTime: 'dusk',
55 | startOffset: 0,
56 | endOffset: 0,
57 | lat: 51.33411,
58 | lon: -0.83716
59 | }
60 | ```
61 |
62 | You can send any combination of those configuration properties. For example, you might just want to set `startTime` and `endTime`, so you only include those properties in the configuration object.
63 |
64 | If you send a message to the input with only the __config object included, the node will consume the message and emit no output.
65 |
66 | If you send a message to the input with the __config object included and/or a payload/topic, the node will firstly process the __config object, remove it from the message and allow the remainder of the message to be emitted as per the configured rules.
67 |
--------------------------------------------------------------------------------
/icons/switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biddster/node-red-contrib-time-range-switch/34ea40a2b62d6f7ad145ec408a8b6b79dca75e68/icons/switch.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
24 |
25 |
76 |
77 |
118 |
119 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-lines-per-function */
2 | /**
3 | The MIT License (MIT)
4 |
5 | Copyright (c) 2016 @biddster
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in
15 | all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
24 | */
25 |
26 | module.exports = function (RED) {
27 | const SunCalc = require('suncalc2');
28 | const MomentRange = require('moment-range');
29 | const _ = require('lodash');
30 |
31 | const Moment = MomentRange.extendMoment(require('moment'));
32 | const fmt = 'YYYY-MM-DD HH:mm';
33 |
34 | const configuration = Object.freeze({
35 | startTime: String,
36 | startOffset: Number,
37 | endTime: String,
38 | endOffset: Number,
39 | lon: Number,
40 | lat: Number,
41 | });
42 |
43 | const timeMatchers = [
44 | (time, now, config) => {
45 | const matches = new RegExp(/(\d+):(\d+):(\d+)/).exec(time);
46 | if (matches && matches.length) {
47 | return now.clone().hour(matches[1]).minute(matches[2]).second(matches[3]);
48 | }
49 | return null;
50 | },
51 | (time, now, config) => {
52 | const matches = new RegExp(/(\d+):(\d+)/).exec(time);
53 | if (matches && matches.length) {
54 | return now.clone().hour(matches[1]).minute(matches[2]).second(0);
55 | }
56 | return null;
57 | },
58 | (time, now, config) => {
59 | // Schedex#57 Suncalc appears to give the best results if you
60 | // calculate at midday.
61 | const sunDate = now.clone().hour(12).minute(0).second(0).toDate();
62 | const sunCalcTimes = SunCalc.getTimes(sunDate, config.lat, config.lon);
63 | const sunTime = sunCalcTimes[time];
64 | if (sunTime) {
65 | // Schedex#57 Nadir appears to work differently to other sun times
66 | // in that it will calculate tomorrow's nadir if the time is
67 | // too close to today's nadir. So we just take the time and
68 | // apply that to the event's moment. That's doesn't yield a
69 | // perfect suntime but it's close enough.
70 | return now
71 | .clone()
72 | .hour(sunTime.getHours())
73 | .minute(sunTime.getMinutes())
74 | .second(sunTime.getSeconds());
75 | }
76 | return null;
77 | },
78 | ];
79 |
80 | RED.nodes.registerType('time-range-switch', function (config) {
81 | RED.nodes.createNode(this, config);
82 |
83 | const momentFor = (time, now) => {
84 | let m = null;
85 | for (let i = 0; i < timeMatchers.length && m == null; ++i) {
86 | m = timeMatchers[i](time, now, config);
87 | }
88 |
89 | if (!m) {
90 | this.status({ fill: 'red', shape: 'dot', text: `Invalid time: ${time}` });
91 | }
92 |
93 | return m;
94 | };
95 |
96 | const calculateStartAndEnd = (now) => {
97 | const start = momentFor(config.startTime, now);
98 | if (config.startOffset) {
99 | start.add(config.startOffset, 'minutes');
100 | }
101 |
102 | const end = momentFor(config.endTime, now);
103 | if (config.endOffset) {
104 | end.add(config.endOffset, 'minutes');
105 | }
106 |
107 | // align end to be before AND within 24 hours of start
108 | while (end.diff(start, 'seconds') < 0) {
109 | // end before start
110 | end.add(1, 'day');
111 | }
112 |
113 | while (end.diff(start, 'seconds') > 86400) {
114 | // end more than day before start
115 | end.subtract(1, 'day');
116 | }
117 |
118 | // move start and end window to be within a day of now
119 | while (end.diff(now, 'seconds') < 0) {
120 | // end before now
121 | start.add(1, 'day');
122 | end.add(1, 'day');
123 | }
124 |
125 | while (end.diff(now, 'seconds') > 86400) {
126 | // end more than day from now
127 | start.subtract(1, 'day');
128 | end.subtract(1, 'day');
129 | }
130 |
131 | return { start, end };
132 | };
133 |
134 | const setInitialStatus = () => {
135 | const { start, end } = calculateStartAndEnd(this.now());
136 | this.status({
137 | fill: 'yellow',
138 | shape: 'dot',
139 | text: `${start.format(fmt)} - ${end.format(fmt)}`,
140 | });
141 | };
142 |
143 | this.on('input', (msg) => {
144 | if (msg.__config) {
145 | _.forIn(configuration, (typeConverter, prop) => {
146 | if (Object.prototype.hasOwnProperty.call(msg.__config, prop)) {
147 | config[prop] = typeConverter(msg.__config[prop]);
148 | }
149 | });
150 |
151 | delete msg.__config;
152 |
153 | // Now try to work out if there was anything in the msg
154 | // other that the standard _msgid. If there is, we'll
155 | // send the message. Otherwise, we assume that the msg
156 | // was solely intended for us to change the configuration.
157 | const keys = _.without(Object.keys(msg), '_msgid');
158 | if (keys.length === 0) {
159 | setInitialStatus();
160 | return;
161 | }
162 | }
163 |
164 | const now = this.now();
165 | const { start, end } = calculateStartAndEnd(now);
166 | const range = Moment.range(start, end);
167 | const output = range.contains(now, { excludeEnd: true }) ? 1 : 2;
168 | const msgs = [];
169 | msgs[output - 1] = msg;
170 | this.send(msgs);
171 |
172 | this.status({
173 | fill: 'green',
174 | shape: output === 1 ? 'dot' : 'ring',
175 | text: `${start.format(fmt)} - ${end.format(fmt)}`,
176 | });
177 | });
178 |
179 | this.now = function () {
180 | return Moment().milliseconds(0);
181 | };
182 |
183 | this.getConfig = function () {
184 | return config;
185 | };
186 |
187 | setInitialStatus();
188 | });
189 | };
190 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-time-range-switch",
3 | "version": "1.2.0",
4 | "description": "",
5 | "main": "index.js",
6 | "keywords": [
7 | "node-red",
8 | "router",
9 | "switch",
10 | "time",
11 | "sun events"
12 | ],
13 | "scripts": {
14 | "start": "mkdir -p .node-red/node_modules && ln -sf $PWD $PWD/.node-red/node_modules/node-red-contrib-schedex && node-red -u .node-red",
15 | "test": "nyc --reporter=html mocha -R spec ./tests/test.js",
16 | "update-dependencies": "./node_modules/.bin/ncu -u && npm install",
17 | "lint": "eslint ."
18 | },
19 | "author": "@biddster",
20 | "license": "MIT",
21 | "dependencies": {
22 | "lodash": "^4.17.21",
23 | "moment": "^2.29.1",
24 | "moment-range": "^4.0.2",
25 | "suncalc2": "^1.8.1"
26 | },
27 | "node-red": {
28 | "nodes": {
29 | "time-range-switch": "index.js"
30 | }
31 | },
32 | "repository": {
33 | "type": "git",
34 | "url": "https://github.com/biddster/node-red-contrib-time-range-switch.git"
35 | },
36 | "bugs": {
37 | "url": "https://github.com/biddster/node-red-contrib-time-range-switch/issues"
38 | },
39 | "devDependencies": {
40 | "@hapi/eslint-config-hapi": "13.0.2",
41 | "@hapi/eslint-plugin-hapi": "4.3.6",
42 | "babel-eslint": "10.1.0",
43 | "eslint": "^8.11.0",
44 | "husky": "^7.0.4",
45 | "lint-staged": "^12.3.7",
46 | "mocha": "^9.2.2",
47 | "node-red": "^2.2.2",
48 | "node-red-contrib-mock-node": "^0.5.3",
49 | "npm-check-updates": "^12.5.4",
50 | "nyc": "^15.1.0",
51 | "prettier": "^2.6.0",
52 | "release-it": "^14.14.3"
53 | },
54 | "husky": {
55 | "hooks": {
56 | "pre-commit": "lint-staged"
57 | }
58 | },
59 | "eslintConfig": {
60 | "extends": "@hapi/eslint-config-hapi",
61 | "parser": "babel-eslint",
62 | "parserOptions": {
63 | "ecmaVersion": 2020,
64 | "sourceType": "script"
65 | },
66 | "overrides": [
67 | {
68 | "files": [
69 | "*.js"
70 | ],
71 | "rules": {
72 | "@hapi/hapi/scope-start": "off",
73 | "comma-dangle": "off",
74 | "brace-style": "off",
75 | "strict": "off"
76 | }
77 | }
78 | ]
79 | },
80 | "prettier": {
81 | "singleQuote": true,
82 | "tabWidth": 4,
83 | "printWidth": 96
84 | },
85 | "nyc": {
86 | "exclude": "tests/**"
87 | },
88 | "lint-staged": {
89 | "*.js": [
90 | "prettier --write",
91 | "eslint",
92 | "git add"
93 | ],
94 | "*.{md,html,json}": [
95 | "prettier --write",
96 | "git add"
97 | ]
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-arrow-callback */
2 | /* eslint-disable no-undef */
3 | /* eslint-disable func-style */
4 | /**
5 | The MIT License (MIT)
6 |
7 | Copyright (c) 2016 @biddster
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy
10 | of this software and associated documentation files (the "Software"), to deal
11 | in the Software without restriction, including without limitation the rights
12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | copies of the Software, and to permit persons to whom the Software is
14 | furnished to do so, subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in
17 | all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | THE SOFTWARE.
26 | */
27 |
28 | const Assert = require('assert');
29 | const _ = require('lodash');
30 | const Moment = require('moment');
31 | const Mock = require('node-red-contrib-mock-node');
32 | const NodeRedModule = require('../index.js');
33 |
34 | function runBetween(start, end, startOffset, endOffset) {
35 | const node = Mock(NodeRedModule, {
36 | startTime: start,
37 | endTime: end,
38 | startOffset,
39 | endOffset,
40 | lat: 51.33411,
41 | lon: -0.83716,
42 | unitTest: true,
43 | });
44 |
45 | const counts = { o1: 0, o2: 0 };
46 | node.send = function (msg) {
47 | if (msg[0]) {
48 | counts.o1++;
49 | }
50 |
51 | if (msg[1]) {
52 | counts.o2++;
53 | }
54 | };
55 |
56 | const time = Moment('2016-01-01');
57 |
58 | node.now = function () {
59 | return time.clone().milliseconds(0);
60 | };
61 |
62 | for (let i = 0; i < 7 * 24; ++i) {
63 | time.add(1, 'hour');
64 | node.emit('input', {});
65 | }
66 |
67 | counts.status = node.status();
68 | return counts;
69 | }
70 |
71 | describe('time-range-switch', function () {
72 | it('should execute programmatic configuration', function (done) {
73 | this.timeout(60000 * 3);
74 | console.log(`\t[${this.test.title}] will take 120-ish seconds, please wait...`);
75 |
76 | const node = Mock(NodeRedModule, {
77 | startTime: '12:35:13',
78 | endTime: 'dusk',
79 | startOffset: 0,
80 | endOffset: 0,
81 | lat: 51.33411,
82 | lon: -0.83716,
83 | unitTest: true,
84 | });
85 |
86 | node.emit('input', {
87 | __config: {
88 | startTime: node.now().format('HH:mm'),
89 | endTime: node.now().add(1, 'minute').format('HH:mm'),
90 | },
91 | });
92 | Assert.strictEqual(node.sent().length, 0);
93 |
94 | node.emit('input', { payload: 'expect output 1' });
95 | Assert.strictEqual(node.sent().length, 1);
96 | let expected = [];
97 | expected[0] = { payload: 'expect output 1' };
98 | Assert.deepStrictEqual(node.sent(0), expected);
99 |
100 | setTimeout(function () {
101 | node.emit('input', { payload: 'expect output 2' });
102 | Assert.strictEqual(node.sent().length, 2);
103 | expected = [];
104 | expected[1] = { payload: 'expect output 2' };
105 | Assert.deepStrictEqual(node.sent(1), expected);
106 | done();
107 | }, 122000);
108 | });
109 | it('should accept programmatic configuration', function () {
110 | const config = {
111 | startTime: '12:35',
112 | endTime: 'dusk',
113 | startOffset: 0,
114 | endOffset: 0,
115 | lat: 51.33411,
116 | lon: -0.83716,
117 | unitTest: true,
118 | };
119 |
120 | const node = Mock(NodeRedModule, config);
121 |
122 | Assert.deepStrictEqual(node.getConfig(), config);
123 |
124 | node.emit('input', { payload: 'whatevs' });
125 |
126 | Assert.deepStrictEqual(node.getConfig(), config);
127 |
128 | const newConfig = {
129 | startTime: '13:33',
130 | endTime: 'night',
131 | startOffset: 1,
132 | endOffset: 2,
133 | lat: 22.33333,
134 | lon: -0.4589,
135 | unitTest: true,
136 | };
137 |
138 | _.forIn(newConfig, (value, key) => {
139 | config[key] = value;
140 | node.emit('input', { __config: { [key]: value } });
141 | Assert.deepStrictEqual(node.getConfig(), config);
142 | });
143 | });
144 |
145 | it('should pass message after programmatic configuration', function () {
146 | const config = {
147 | startTime: '12:35',
148 | endTime: 'dusk',
149 | startOffset: 0,
150 | endOffset: 0,
151 | lat: 51.33411,
152 | lon: -0.83716,
153 | unitTest: true,
154 | };
155 |
156 | const node = Mock(NodeRedModule, config);
157 | node.now = function () {
158 | return Moment('2021-03-16T23:51:00').milliseconds(0);
159 | };
160 |
161 | Assert.deepStrictEqual(node.getConfig(), config);
162 |
163 | config.startTime = '14:44';
164 | node.emit('input', { __config: { startTime: '14:44' } });
165 | Assert.deepStrictEqual(node.getConfig(), config);
166 |
167 | Assert.strictEqual(node.sent().length, 0);
168 |
169 | config.endTime = '15:55';
170 | node.emit('input', { __config: { endTime: '15:55' }, payload: 'emit me' });
171 | Assert.deepStrictEqual(node.getConfig(), config);
172 |
173 | Assert.strictEqual(node.sent().length, 1);
174 | const expected = [];
175 | expected[1] = { payload: 'emit me' };
176 | Assert.deepStrictEqual(node.sent(0), expected);
177 | });
178 |
179 | // TODO - all these tests should assert the actual times rather than just the counts.
180 | it('should work between 12:45...02:45', function () {
181 | const counts = runBetween('12:45', '02:45');
182 | Assert.strictEqual(98, counts.o1);
183 | Assert.strictEqual(70, counts.o2);
184 | Assert.strictEqual(counts.status.text, '2016-01-07 12:45 - 2016-01-08 02:45');
185 | });
186 | it('should work between 01:45...02:45', function () {
187 | const counts = runBetween('01:45', '02:45');
188 | Assert.strictEqual(7, counts.o1);
189 | Assert.strictEqual(161, counts.o2);
190 | Assert.strictEqual(counts.status.text, '2016-01-08 01:45 - 2016-01-08 02:45');
191 | });
192 | it('should work between 11:45...12:45', function () {
193 | const counts = runBetween('11:45', '12:45');
194 | Assert.strictEqual(7, counts.o1);
195 | Assert.strictEqual(161, counts.o2);
196 | Assert.strictEqual(counts.status.text, '2016-01-08 11:45 - 2016-01-08 12:45');
197 | });
198 | it('should work between 22:45...01:45', function () {
199 | const counts = runBetween('22:45', '01:45');
200 | Assert.strictEqual(21, counts.o1);
201 | Assert.strictEqual(147, counts.o2);
202 | Assert.strictEqual(counts.status.text, '2016-01-07 22:45 - 2016-01-08 01:45');
203 | });
204 | it('should work between 06:30...03:30', function () {
205 | const counts = runBetween('06:30', '03:30');
206 | Assert.strictEqual(147, counts.o1);
207 | Assert.strictEqual(21, counts.o2);
208 | Assert.strictEqual(counts.status.text, '2016-01-07 06:30 - 2016-01-08 03:30');
209 | });
210 | it('should work between dawn...dusk', function () {
211 | const counts = runBetween('dawn', 'dusk');
212 | Assert.strictEqual(63, counts.o1);
213 | Assert.strictEqual(105, counts.o2);
214 | Assert.strictEqual(counts.status.text, '2016-01-08 07:28 - 2016-01-08 16:53');
215 | });
216 | it('should work between goldenHour...dawn', function () {
217 | const counts = runBetween('goldenHour', 'dawn');
218 | Assert.strictEqual(112, counts.o1);
219 | Assert.strictEqual(56, counts.o2);
220 | Assert.strictEqual(counts.status.text, '2016-01-07 15:15 - 2016-01-08 07:28');
221 | });
222 | it('should work between 22:45...01:45 with a start offset of 16', function () {
223 | const counts = runBetween('22:45', '01:45', 16);
224 | Assert.strictEqual(14, counts.o1);
225 | Assert.strictEqual(154, counts.o2);
226 | Assert.strictEqual(counts.status.text, '2016-01-07 23:01 - 2016-01-08 01:45');
227 | });
228 | it('should work between 22:45...01:45 with an end offset of -46', function () {
229 | const counts = runBetween('22:45', '01:45', 0, -46);
230 | Assert.strictEqual(14, counts.o1);
231 | Assert.strictEqual(154, counts.o2);
232 | Assert.strictEqual(counts.status.text, '2016-01-07 22:45 - 2016-01-08 00:59');
233 | });
234 | it('issue 26', function () {
235 | const invocations = [
236 | ['2019-10-23 19:29:25', 2],
237 | ['2019-10-23 20:29:25', 2],
238 | ['2019-10-23 21:29:25', 2],
239 | ['2019-10-23 22:29:25', 1],
240 | ['2019-10-23 23:29:25', 1],
241 | ['2019-10-24 00:29:25', 1],
242 | ['2019-10-24 01:29:25', 1],
243 | ['2019-10-24 02:29:25', 1],
244 | ['2019-10-24 03:29:25', 1],
245 | ['2019-10-24 04:29:25', 1],
246 | ['2019-10-24 05:29:25', 1],
247 | ['2019-10-24 06:29:25', 2],
248 | ['2019-10-24 07:29:25', 2],
249 | ['2019-10-24 08:29:25', 2],
250 | ['2019-10-24 09:29:25', 2],
251 | ['2019-10-24 10:29:25', 2],
252 | ['2019-10-24 11:29:25', 2],
253 | ['2019-10-24 12:29:25', 2],
254 | ['2019-10-24 13:29:25', 2],
255 | ['2019-10-24 14:29:25', 2],
256 | ['2019-10-24 15:29:25', 2],
257 | ['2019-10-24 16:29:25', 2],
258 | ['2019-10-24 17:29:25', 2],
259 | ['2019-10-24 18:29:25', 2],
260 | ];
261 |
262 | const node = Mock(NodeRedModule, {
263 | startTime: '22:00',
264 | endTime: '06:00',
265 | lat: 48.2205998,
266 | lon: 16.239978,
267 | unitTest: true,
268 | });
269 |
270 | function findOutput(msgs) {
271 | if (msgs[0]) {
272 | return 1;
273 | }
274 |
275 | if (msgs[1]) {
276 | return 2;
277 | }
278 |
279 | throw new Error('No output');
280 | }
281 |
282 | invocations.forEach(function (invocation, i) {
283 | const time = Moment(invocation[0]);
284 |
285 | node.now = function () {
286 | return time.clone();
287 | };
288 |
289 | node.emit('input', { payload: 'fire' });
290 | const msgs = node.sent(i);
291 | const output = findOutput(msgs);
292 | // console.log(time.toString() + ', output ' + output);
293 | Assert.strictEqual(invocation[1], output);
294 | });
295 | });
296 | });
297 |
--------------------------------------------------------------------------------