Skip to content

Commit 55c8f97

Browse files
committed
feat: support for server time
Closes #709, closes #579
1 parent 9374ccb commit 55c8f97

File tree

9 files changed

+130
-17
lines changed

9 files changed

+130
-17
lines changed

.devcontainer/ui-lovelace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,7 @@ views:
13141314
const tomorrow = new Date(start.getTime() + 25 * 1000 * 60 * 60);
13151315
const tomorrow2 = new Date(start.getTime() + 28 * 1000 * 60 * 60);
13161316
return [
1317+
[start.getTime(), 5],
13171318
[tomorrow.getTime(), 30],
13181319
[tomorrow2.getTime(), 20]
13191320
];

hacs.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"name": "apexcharts-card",
3-
"render_readme": true
2+
"name": "apexcharts-card",
3+
"homeassistant": "2023.7.0",
4+
"render_readme": true
45
}

package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"moment": "^2.30.1",
4141
"moment-duration-format": "^2.3.2",
4242
"moment-range": "^4.0.2",
43+
"moment-timezone": "^0.5.45",
4344
"parse-duration": "^1.1.0",
4445
"spark-md5": "^3.0.2",
4546
"tinycolor": "^0.0.1",

src/apexcharts-card.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {
3838
validateInterval,
3939
validateOffset,
4040
getLovelace,
41+
isUsingServerTimezone,
42+
computeTimezoneDiffWithLocal,
4143
} from './utils';
4244
import ApexCharts from 'apexcharts';
4345
import { Ripple } from '@material/mwc-ripple';
@@ -80,6 +82,7 @@ import {
8082
import parse from 'parse-duration';
8183
import tinycolor from '@ctrl/tinycolor';
8284
import { actionHandler } from './action-handler-directive';
85+
import { OverrideFrontendLocaleData } from './types-ha';
8386

8487
/* eslint no-console: 0 */
8588
console.info(
@@ -163,6 +166,8 @@ class ChartsCard extends LitElement {
163166

164167
private _yAxisConfig?: ChartCardYAxis[];
165168

169+
private _serverTimeOffset = 0;
170+
166171
@property({ attribute: false }) _lastUpdated: Date = new Date();
167172

168173
@property({ type: Boolean }) private _warning = false;
@@ -287,6 +292,7 @@ class ChartsCard extends LitElement {
287292
this._loaded = false;
288293
this._dataLoaded = false;
289294
this._updating = false;
295+
this._serverTimeOffset = 0;
290296
if (this._apexBrush) {
291297
this._apexBrush.destroy();
292298
this._apexBrush = undefined;
@@ -753,6 +759,10 @@ class ChartsCard extends LitElement {
753759
private async _initialLoad() {
754760
await this.updateComplete;
755761

762+
if (isUsingServerTimezone(this._hass)) {
763+
this._serverTimeOffset = computeTimezoneDiffWithLocal(this._hass?.config.time_zone);
764+
}
765+
756766
if (!this._apexChart && this.shadowRoot && this._config && this.shadowRoot.querySelector('#graph')) {
757767
this._loaded = true;
758768
const graph = this.shadowRoot.querySelector('#graph');
@@ -826,33 +836,41 @@ class ChartsCard extends LitElement {
826836
const offset = (this._seriesOffset[index] || 0) - (this._seriesTimeDelta[index] || 0);
827837
if (offset) {
828838
data = offsetData(graph.history, offset);
839+
} else if (this._serverTimeOffset) {
840+
data = offsetData(graph.history, this._serverTimeOffset);
829841
} else {
830842
data = [...graph.history];
831843
}
832844
if (this._config?.series[index].type !== 'column' && this._config?.series[index].extend_to) {
833845
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
834846
const lastPoint = data.slice(-1)[0]!;
835-
if (this._config?.series[index].extend_to === 'end' && lastPoint[0] < end.getTime()) {
847+
if (
848+
this._config?.series[index].extend_to === 'end' &&
849+
lastPoint[0] < end.getTime() - this._serverTimeOffset
850+
) {
836851
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
837-
data.push([end.getTime(), lastPoint[1]]);
838-
} else if (this._config?.series[index].extend_to === 'now' && lastPoint[0] < now.getTime()) {
852+
data.push([end.getTime() - this._serverTimeOffset, lastPoint[1]]);
853+
} else if (
854+
this._config?.series[index].extend_to === 'now' &&
855+
lastPoint[0] < now.getTime() - this._serverTimeOffset
856+
) {
839857
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
840-
data.push([now.getTime(), lastPoint[1]]);
858+
data.push([now.getTime() - this._serverTimeOffset, lastPoint[1]]);
841859
}
842860
}
843861
const result = this._config?.series[index].invert ? { data: this._invertData(data) } : { data };
844862
if (this._config?.series[index].show.in_chart) graphData.series.push(result);
845863
if (this._config?.series[index].show.in_brush) brushData.series.push(result);
846864
return;
847865
});
848-
graphData.annotations = this._computeAnnotations(start, end, now);
866+
graphData.annotations = this._computeAnnotations(start, end, new Date(now.getTime() - this._serverTimeOffset));
849867
if (this._yAxisConfig) {
850868
graphData.yaxis = this._computeYAxisAutoMinMax(start, end);
851869
}
852870
if (!this._apexBrush) {
853871
graphData.xaxis = {
854-
min: start.getTime(),
855-
max: this._findEndOfChart(end, false),
872+
min: start.getTime() - this._serverTimeOffset,
873+
max: this._findEndOfChart(new Date(end.getTime() - this._serverTimeOffset), false),
856874
};
857875
}
858876
} else {
@@ -949,8 +967,8 @@ class ChartsCard extends LitElement {
949967
TIMESERIES_TYPES.includes(this._config.chart_type) ? false : true,
950968
);
951969
if (this._apexBrush) {
952-
const newMin = start.getTime();
953-
const newMax = this._findEndOfChart(end, false);
970+
const newMin = start.getTime() - this._serverTimeOffset;
971+
const newMax = this._findEndOfChart(new Date(end.getTime() - this._serverTimeOffset), false);
954972
brushData.xaxis = {
955973
min: newMin,
956974
max: newMax,
@@ -1008,6 +1026,7 @@ class ChartsCard extends LitElement {
10081026
? new Date(start.getTime() + this._seriesOffset[index]).getTime()
10091027
: start.getTime(),
10101028
this._seriesOffset[index] ? new Date(end.getTime() + this._seriesOffset[index]).getTime() : end.getTime(),
1029+
this._serverTimeOffset,
10111030
) || {
10121031
min: [0, null],
10131032
max: [0, null],
@@ -1444,14 +1463,18 @@ class ChartsCard extends LitElement {
14441463
private _getSpanDates(): { start: Date; end: Date } {
14451464
let end = new Date();
14461465
let start = new Date(end.getTime() - this._graphSpan + 1);
1447-
// Span
1466+
const curMoment = moment();
1467+
if ((this._hass?.locale as OverrideFrontendLocaleData).time_zone === 'server') {
1468+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1469+
curMoment.tz(this._hass!.config.time_zone);
1470+
}
14481471
if (this._config?.span?.start) {
14491472
// Just Span
1450-
const startM = moment().startOf(this._config.span.start);
1473+
const startM = curMoment.startOf(this._config.span.start);
14511474
start = startM.toDate();
14521475
end = new Date(start.getTime() + this._graphSpan);
14531476
} else if (this._config?.span?.end) {
1454-
const endM = moment().endOf(this._config.span.end);
1477+
const endM = curMoment.endOf(this._config.span.end);
14551478
end = new Date(endM.toDate().getTime() + 1);
14561479
start = new Date(end.getTime() - this._graphSpan + 1);
14571480
}

src/const.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Moment from 'moment';
1+
import Moment from 'moment-timezone';
22
import { extendMoment } from 'moment-range';
33
import momentDurationFormatSetup from 'moment-duration-format';
44

src/graphEntry.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,15 @@ export default class GraphEntry {
135135
return Math.max(...this._computedHistory.flatMap((item) => (item[1] === null ? [] : [item[1]])));
136136
}
137137

138-
public minMaxWithTimestamp(start: number, end: number): { min: HistoryPoint; max: HistoryPoint } | undefined {
138+
public minMaxWithTimestamp(
139+
start: number,
140+
end: number,
141+
serverOffset = 0,
142+
): { min: HistoryPoint; max: HistoryPoint } | undefined {
139143
if (!this._computedHistory || this._computedHistory.length === 0) return undefined;
140144
if (this._computedHistory.length === 1)
141145
return { min: [start, this._computedHistory[0][1]], max: [end, this._computedHistory[0][1]] };
142-
return this._computedHistory.reduce(
146+
const minMax = this._computedHistory.reduce(
143147
(acc: { min: HistoryPoint; max: HistoryPoint }, point) => {
144148
if (point[1] === null) return acc;
145149
if (point[0] > end || point[0] < start) return acc;
@@ -149,6 +153,11 @@ export default class GraphEntry {
149153
},
150154
{ min: [0, null], max: [0, null] },
151155
);
156+
if (serverOffset) {
157+
if (minMax.min[0]) minMax.min[0] -= serverOffset;
158+
if (minMax.max[0]) minMax.max[0] -= serverOffset;
159+
}
160+
return minMax;
152161
}
153162

154163
public minMaxWithTimestampForYAxis(start: number, end: number): { min: HistoryPoint; max: HistoryPoint } | undefined {

src/types-ha.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export enum NumberFormat {
2+
language = 'language',
3+
system = 'system',
4+
comma_decimal = 'comma_decimal',
5+
decimal_comma = 'decimal_comma',
6+
space_comma = 'space_comma',
7+
none = 'none',
8+
}
9+
10+
export enum TimeFormat {
11+
language = 'language',
12+
system = 'system',
13+
am_pm = '12',
14+
twenty_four = '24',
15+
}
16+
17+
export enum TimeZone {
18+
local = 'local',
19+
server = 'server',
20+
}
21+
22+
export enum DateFormat {
23+
language = 'language',
24+
system = 'system',
25+
DMY = 'DMY',
26+
MDY = 'MDY',
27+
YMD = 'YMD',
28+
}
29+
30+
export enum FirstWeekday {
31+
language = 'language',
32+
monday = 'monday',
33+
tuesday = 'tuesday',
34+
wednesday = 'wednesday',
35+
thursday = 'thursday',
36+
friday = 'friday',
37+
saturday = 'saturday',
38+
sunday = 'sunday',
39+
}
40+
41+
export interface OverrideFrontendLocaleData {
42+
language: string;
43+
number_format: NumberFormat;
44+
time_format: TimeFormat;
45+
date_format: DateFormat;
46+
first_weekday: FirstWeekday;
47+
time_zone: TimeZone;
48+
}

src/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import parse from 'parse-duration';
66
import { ChartCardExternalConfig, ChartCardPrettyTime, ChartCardSeriesExternalConfig } from './types-config';
77
import { DEFAULT_FLOAT_PRECISION, DEFAULT_MAX, DEFAULT_MIN, moment, NO_VALUE } from './const';
88
import { formatNumber, FrontendLocaleData, HomeAssistant } from 'custom-card-helpers';
9+
import { OverrideFrontendLocaleData } from './types-ha';
910

1011
export function compress(data: unknown): string {
1112
return lzStringCompress(JSON.stringify(data));
@@ -325,3 +326,12 @@ export function myFormatNumber(
325326
maximumFractionDigits: precision === undefined ? DEFAULT_FLOAT_PRECISION : precision,
326327
});
327328
}
329+
330+
export function computeTimezoneDiffWithLocal(timezone: string | undefined): number {
331+
if (!timezone) return 0;
332+
return (moment().utcOffset() - moment().tz(timezone).utcOffset()) * 60 * 1000;
333+
}
334+
335+
export function isUsingServerTimezone(/*config: ChartCardConfig, */ hass: HomeAssistant | undefined): boolean {
336+
return (hass?.locale as OverrideFrontendLocaleData).time_zone === 'server';
337+
}

0 commit comments

Comments
 (0)