diff --git a/lib/extension/bridgeConfig.js b/lib/extension/bridgeConfig.js index 53f8d155..c55aca06 100644 --- a/lib/extension/bridgeConfig.js +++ b/lib/extension/bridgeConfig.js @@ -76,7 +76,7 @@ class BridgeConfig { } lastSeen(topic, message) { - const allowed = ['disable', 'ISO_8601', 'epoch']; + const allowed = ['disable', 'ISO_8601', 'epoch', 'ISO_8601_local']; message = message.toString(); if (!allowed.includes(message)) { diff --git a/lib/extension/deviceReceive.js b/lib/extension/deviceReceive.js index 9c68186f..40062e11 100644 --- a/lib/extension/deviceReceive.js +++ b/lib/extension/deviceReceive.js @@ -1,5 +1,6 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); +const utils = require('../util/utils'); const dontCacheProperties = [ 'action', 'button', 'button_left', 'button_right', 'click', 'forgotten', 'keyerror', @@ -123,6 +124,9 @@ class DeviceReceive { case 'ISO_8601': payload.last_seen = new Date(now).toISOString(); break; + case 'ISO_8601_local': + payload.last_seen = utils.toLocalISOString(new Date(now)); + break; case 'epoch': payload.last_seen = now; break; diff --git a/lib/util/settings.js b/lib/util/settings.js index 2157febb..4827c1d0 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -44,6 +44,7 @@ const defaults = { /** * Add a last_seen attribute to mqtt messages, contains date/time of zigbee message arrival * "ISO_8601": ISO 8601 format + * "ISO_8601_local": Local ISO 8601 format (instead of UTC-based) * "epoch": milliseconds elapsed since the UNIX epoch * "disable": no last_seen attribute (default) */ diff --git a/lib/util/utils.js b/lib/util/utils.js index 374a13a3..ca463f67 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -33,6 +33,28 @@ function resolveEntity(ID) { return {ID: ID, type: type, friendlyName: friendlyName}; } +// construct a local ISO8601 string (instead of UTC-based) +// Example: +// - ISO8601 (UTC) = 2019-03-01T15:32:45.941+0000 +// - ISO8601 (local) = 2019-03-01T16:32:45.941+0100 (for timezone GMT+1) +function toLocalISOString(dDate) { + const tzOffset = -dDate.getTimezoneOffset(); + const plusOrMinus = tzOffset >= 0 ? '+' : '-'; + const pad = function(num) { + const norm = Math.floor(Math.abs(num)); + return (norm < 10 ? '0' : '') + norm; + }; + + return dDate.getFullYear() + + '-' + pad(dDate.getMonth() + 1) + + '-' + pad(dDate.getDate()) + + 'T' + pad(dDate.getHours()) + + ':' + pad(dDate.getMinutes()) + + ':' + pad(dDate.getSeconds()) + + plusOrMinus + pad(tzOffset / 60) + + ':' + pad(tzOffset % 60); +} + module.exports = { millisecondsToSeconds: (milliseconds) => milliseconds / 1000, secondsToMilliseconds: (seconds) => seconds * 1000, @@ -40,4 +62,5 @@ module.exports = { isIkeaTradfriDevice: (device) => ikeaTradfriManufacturerID.includes(device.manufId), isNumeric: (string) => /^\d+$/.test(string), resolveEntity: (ID) => resolveEntity(ID), + toLocalISOString: (dDate) => toLocalISOString(dDate), }; diff --git a/test/deviceReceive.test.js b/test/deviceReceive.test.js index 1d2e9ef8..3d903da3 100644 --- a/test/deviceReceive.test.js +++ b/test/deviceReceive.test.js @@ -225,5 +225,20 @@ describe('DeviceReceive', () => { chai.assert.isTrue(publishEntityState.calledOnce); chai.assert.equal(typeof publishEntityState.getCall(0).args[1].last_seen, 'string'); }); + + it('Should publish last_seen ISO_8601_local', () => { + const device = {ieeeAddr: '0x12345678'}; + const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 1); + sandbox.stub(settings, 'get').callsFake(() => { + return { + advanced: { + last_seen: 'ISO_8601_local', + }, + }; + }); + deviceReceive.onZigbeeMessage(message, device, WXKG02LM); + chai.assert.isTrue(publishEntityState.calledOnce); + chai.assert.equal(typeof publishEntityState.getCall(0).args[1].last_seen, 'string'); + }); }); });