├── .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 | --------------------------------------------------------------------------------