From ebc229d525055efe1cbf52e15772bbddf10909a6 Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Leymarie Date: Mon, 20 Jan 2025 17:17:56 +0100 Subject: [PATCH] Chart: Fix timezone issues --- .../device.getDeviceFeaturesAggregates.js | 5 ++-- ...device.getDeviceFeaturesAggregates.test.js | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/server/lib/device/device.getDeviceFeaturesAggregates.js b/server/lib/device/device.getDeviceFeaturesAggregates.js index 7a678f7fc1..dc5830bbf7 100644 --- a/server/lib/device/device.getDeviceFeaturesAggregates.js +++ b/server/lib/device/device.getDeviceFeaturesAggregates.js @@ -74,8 +74,9 @@ async function getDeviceFeaturesAggregates(selector, intervalInMinutes, maxState const isBinary = ['binary', 'push'].includes(deviceFeature.type); - const now = new Date(); - const intervalDate = new Date(now.getTime() - intervalInMinutes * 60 * 1000); + // Get the interval date, and offset in UTC + const intervalDate = new Date(Date.now() - intervalInMinutes * 60 * 1000); + intervalDate.setMinutes(intervalDate.getMinutes() - intervalDate.getTimezoneOffset()); let values; diff --git a/server/test/lib/device/device.getDeviceFeaturesAggregates.test.js b/server/test/lib/device/device.getDeviceFeaturesAggregates.test.js index a68fdf9a76..ecff792d0e 100644 --- a/server/test/lib/device/device.getDeviceFeaturesAggregates.test.js +++ b/server/test/lib/device/device.getDeviceFeaturesAggregates.test.js @@ -2,6 +2,10 @@ const EventEmitter = require('events'); const { expect, assert } = require('chai'); const sinon = require('sinon'); const { fake } = require('sinon'); +const dayjs = require('dayjs'); +const utc = require('dayjs/plugin/utc'); +const timezone = require('dayjs/plugin/timezone'); + const db = require('../../../models'); const Device = require('../../../lib/device'); const Job = require('../../../lib/job'); @@ -9,6 +13,10 @@ const Job = require('../../../lib/job'); const event = new EventEmitter(); const job = new Job(event); +// Extend Day.js with plugins +dayjs.extend(utc); +dayjs.extend(timezone); + const insertStates = async (intervalInMinutes) => { const deviceFeatureStateToInsert = []; const now = new Date(); @@ -95,6 +103,27 @@ describe('Device.getDeviceFeaturesAggregates non binary feature', function Descr expect(device).to.have.property('name'); expect(deviceFeature).to.have.property('name'); }); + it('should test timezone behavior', async () => { + // Create current date in Toronto timezone, 30 minutes ago + const torontoDate = dayjs() + .tz('America/Toronto') + .subtract(30, 'minute') + .toDate(); + + await db.duckDbInsertState('ca91dfdf-55b2-4cf8-a58b-99c0fbf6f5e4', 1, torontoDate); + const variable = { + getValue: fake.resolves(null), + }; + const stateManager = { + get: fake.returns({ + id: 'ca91dfdf-55b2-4cf8-a58b-99c0fbf6f5e4', + name: 'my-feature', + }), + }; + const deviceInstance = new Device(event, {}, stateManager, {}, {}, variable, job); + const { values } = await deviceInstance.getDeviceFeaturesAggregates('test-device-feature', 60, 100); + expect(values).to.have.lengthOf(1); + }); it('should return last day states', async () => { await insertStates(48 * 60); const variable = {