Commit 3ed49e3f026437973515a48221c1a71a85e16db7

Authored by Igor Kulikov
1 parent d184d8db

Add timezone support for timewindow

@@ -18,8 +18,8 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/tel @@ -18,8 +18,8 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/tel
18 import { 18 import {
19 AggregationType, 19 AggregationType,
20 calculateIntervalEndTime, 20 calculateIntervalEndTime,
21 - calculateIntervalStartTime,  
22 - QuickTimeInterval 21 + calculateIntervalStartTime, getCurrentTime,
  22 + QuickTimeInterval, SubscriptionTimewindow
23 } from '@shared/models/time/time.models'; 23 } from '@shared/models/time/time.models';
24 import { UtilsService } from '@core/services/utils.service'; 24 import { UtilsService } from '@core/services/utils.service';
25 import { deepClone } from '@core/utils'; 25 import { deepClone } from '@core/utils';
@@ -78,34 +78,29 @@ export class DataAggregator { @@ -78,34 +78,29 @@ export class DataAggregator {
78 private resetPending = false; 78 private resetPending = false;
79 private updatedData = false; 79 private updatedData = false;
80 80
81 - private noAggregation = this.aggregationType === AggregationType.NONE;  
82 - private aggregationTimeout = Math.max(this.interval, 1000); 81 + private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE;
  82 + private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
83 private readonly aggFunction: AggFunction; 83 private readonly aggFunction: AggFunction;
84 84
85 private intervalTimeoutHandle: Timeout; 85 private intervalTimeoutHandle: Timeout;
86 private intervalScheduledTime: number; 86 private intervalScheduledTime: number;
87 87
  88 + private startTs = this.subsTw.startTs + this.subsTw.tsOffset;
88 private endTs: number; 89 private endTs: number;
89 private elapsed: number; 90 private elapsed: number;
90 91
91 constructor(private onDataCb: onAggregatedData, 92 constructor(private onDataCb: onAggregatedData,
92 private tsKeyNames: string[], 93 private tsKeyNames: string[],
93 - private startTs: number,  
94 - private limit: number,  
95 - private aggregationType: AggregationType,  
96 - private timeWindow: number,  
97 - private interval: number,  
98 - private stateData: boolean, 94 + private subsTw: SubscriptionTimewindow,
99 private utils: UtilsService, 95 private utils: UtilsService,
100 - private ignoreDataUpdateOnIntervalTick: boolean,  
101 - private quickInterval: QuickTimeInterval) { 96 + private ignoreDataUpdateOnIntervalTick: boolean) {
102 this.tsKeyNames.forEach((key) => { 97 this.tsKeyNames.forEach((key) => {
103 this.dataBuffer[key] = []; 98 this.dataBuffer[key] = [];
104 }); 99 });
105 - if (this.stateData) { 100 + if (this.subsTw.aggregation.stateData) {
106 this.lastPrevKvPairData = {}; 101 this.lastPrevKvPairData = {};
107 } 102 }
108 - switch (this.aggregationType) { 103 + switch (this.subsTw.aggregation.type) {
109 case AggregationType.MIN: 104 case AggregationType.MIN:
110 this.aggFunction = min; 105 this.aggFunction = min;
111 break; 106 break;
@@ -135,19 +130,21 @@ export class DataAggregator { @@ -135,19 +130,21 @@ export class DataAggregator {
135 return prevOnDataCb; 130 return prevOnDataCb;
136 } 131 }
137 132
138 - public reset(startTs: number, timeWindow: number, interval: number) { 133 + public reset(subsTw: SubscriptionTimewindow) {
139 if (this.intervalTimeoutHandle) { 134 if (this.intervalTimeoutHandle) {
140 clearTimeout(this.intervalTimeoutHandle); 135 clearTimeout(this.intervalTimeoutHandle);
141 this.intervalTimeoutHandle = null; 136 this.intervalTimeoutHandle = null;
142 } 137 }
  138 + this.subsTw = subsTw;
143 this.intervalScheduledTime = this.utils.currentPerfTime(); 139 this.intervalScheduledTime = this.utils.currentPerfTime();
144 - this.startTs = startTs;  
145 - this.timeWindow = timeWindow;  
146 - this.interval = interval;  
147 - const endTs = this.startTs + this.timeWindow;  
148 - this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); 140 + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
  141 + if (this.subsTw.quickInterval) {
  142 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
  143 + } else {
  144 + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
  145 + }
149 this.elapsed = 0; 146 this.elapsed = 0;
150 - this.aggregationTimeout = Math.max(this.interval, 1000); 147 + this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
151 this.resetPending = true; 148 this.resetPending = true;
152 this.updatedData = false; 149 this.updatedData = false;
153 this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); 150 this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout);
@@ -168,8 +165,11 @@ export class DataAggregator { @@ -168,8 +165,11 @@ export class DataAggregator {
168 if (!this.dataReceived) { 165 if (!this.dataReceived) {
169 this.elapsed = 0; 166 this.elapsed = 0;
170 this.dataReceived = true; 167 this.dataReceived = true;
171 - const endTs = this.startTs + this.timeWindow;  
172 - this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); 168 + if (this.subsTw.quickInterval) {
  169 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
  170 + } else {
  171 + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
  172 + }
173 } 173 }
174 if (this.resetPending) { 174 if (this.resetPending) {
175 this.resetPending = false; 175 this.resetPending = false;
@@ -203,15 +203,19 @@ export class DataAggregator { @@ -203,15 +203,19 @@ export class DataAggregator {
203 this.intervalTimeoutHandle = null; 203 this.intervalTimeoutHandle = null;
204 } 204 }
205 if (!history) { 205 if (!history) {
206 - const delta = Math.floor(this.elapsed / this.interval); 206 + const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval);
207 if (delta || !this.data) { 207 if (delta || !this.data) {
208 - const tickTs = delta * this.interval;  
209 - const startTS = this.startTs + tickTs;  
210 - this.startTs = calculateIntervalStartTime(this.quickInterval, startTS);  
211 - const endTs = this.endTs + tickTs;  
212 - this.endTs = calculateIntervalEndTime(this.quickInterval, endTs); 208 + const tickTs = delta * this.subsTw.aggregation.interval;
  209 + if (this.subsTw.quickInterval) {
  210 + const currentDate = getCurrentTime(this.subsTw.timezone);
  211 + this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  212 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  213 + } else {
  214 + this.startTs += tickTs;
  215 + this.endTs += tickTs;
  216 + }
213 this.data = this.updateData(); 217 this.data = this.updateData();
214 - this.elapsed = this.elapsed - delta * this.interval; 218 + this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval;
215 } 219 }
216 } else { 220 } else {
217 this.data = this.updateData(); 221 this.data = this.updateData();
@@ -234,7 +238,7 @@ export class DataAggregator { @@ -234,7 +238,7 @@ export class DataAggregator {
234 let keyData = this.dataBuffer[key]; 238 let keyData = this.dataBuffer[key];
235 aggKeyData.forEach((aggData, aggTimestamp) => { 239 aggKeyData.forEach((aggData, aggTimestamp) => {
236 if (aggTimestamp <= this.startTs) { 240 if (aggTimestamp <= this.startTs) {
237 - if (this.stateData && 241 + if (this.subsTw.aggregation.stateData &&
238 (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { 242 (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) {
239 this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; 243 this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue];
240 } 244 }
@@ -246,11 +250,11 @@ export class DataAggregator { @@ -246,11 +250,11 @@ export class DataAggregator {
246 } 250 }
247 }); 251 });
248 keyData.sort((set1, set2) => set1[0] - set2[0]); 252 keyData.sort((set1, set2) => set1[0] - set2[0]);
249 - if (this.stateData) { 253 + if (this.subsTw.aggregation.stateData) {
250 this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); 254 this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key]));
251 } 255 }
252 - if (keyData.length > this.limit) {  
253 - keyData = keyData.slice(keyData.length - this.limit); 256 + if (keyData.length > this.subsTw.aggregation.limit) {
  257 + keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit);
254 } 258 }
255 this.dataBuffer[key] = keyData; 259 this.dataBuffer[key] = keyData;
256 } 260 }
@@ -286,7 +290,7 @@ export class DataAggregator { @@ -286,7 +290,7 @@ export class DataAggregator {
286 } 290 }
287 291
288 private processAggregatedData(data: SubscriptionData): AggregationMap { 292 private processAggregatedData(data: SubscriptionData): AggregationMap {
289 - const isCount = this.aggregationType === AggregationType.COUNT; 293 + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
290 const aggregationMap: AggregationMap = {}; 294 const aggregationMap: AggregationMap = {};
291 for (const key of Object.keys(data)) { 295 for (const key of Object.keys(data)) {
292 let aggKeyData = aggregationMap[key]; 296 let aggKeyData = aggregationMap[key];
@@ -311,7 +315,7 @@ export class DataAggregator { @@ -311,7 +315,7 @@ export class DataAggregator {
311 } 315 }
312 316
313 private updateAggregatedData(data: SubscriptionData) { 317 private updateAggregatedData(data: SubscriptionData) {
314 - const isCount = this.aggregationType === AggregationType.COUNT; 318 + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT;
315 for (const key of Object.keys(data)) { 319 for (const key of Object.keys(data)) {
316 let aggKeyData = this.aggregationMap[key]; 320 let aggKeyData = this.aggregationMap[key];
317 if (!aggKeyData) { 321 if (!aggKeyData) {
@@ -323,7 +327,8 @@ export class DataAggregator { @@ -323,7 +327,8 @@ export class DataAggregator {
323 const timestamp = kvPair[0]; 327 const timestamp = kvPair[0];
324 const value = this.convertValue(kvPair[1]); 328 const value = this.convertValue(kvPair[1]);
325 const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + 329 const aggTimestamp = this.noAggregation ? timestamp : (this.startTs +
326 - Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2); 330 + Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) *
  331 + this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2);
327 let aggData = aggKeyData.get(aggTimestamp); 332 let aggData = aggKeyData.get(aggTimestamp);
328 if (!aggData) { 333 if (!aggData) {
329 aggData = { 334 aggData = {
@@ -237,7 +237,7 @@ export class EntityDataSubscription { @@ -237,7 +237,7 @@ export class EntityDataSubscription {
237 }; 237 };
238 238
239 if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { 239 if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
240 - this.prepareSubscriptionCommands(this.dataCommand); 240 + this.prepareSubscriptionCommands();
241 } 241 }
242 242
243 this.subscriber.subscriptionCommands.push(this.dataCommand); 243 this.subscriber.subscriptionCommands.push(this.dataCommand);
@@ -256,8 +256,8 @@ export class EntityDataSubscription { @@ -256,8 +256,8 @@ export class EntityDataSubscription {
256 if (this.started) { 256 if (this.started) {
257 const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; 257 const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand;
258 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && 258 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries &&
259 - !this.history && this.tsFields.length) {  
260 - const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription(); 259 + !this.history && this.tsFields.length) {
  260 + const newSubsTw = this.listener.updateRealtimeSubscription();
261 this.subsTw = newSubsTw; 261 this.subsTw = newSubsTw;
262 targetCommand.tsCmd.startTs = this.subsTw.startTs; 262 targetCommand.tsCmd.startTs = this.subsTw.startTs;
263 targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; 263 targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow;
@@ -266,9 +266,10 @@ export class EntityDataSubscription { @@ -266,9 +266,10 @@ export class EntityDataSubscription {
266 targetCommand.tsCmd.agg = this.subsTw.aggregation.type; 266 targetCommand.tsCmd.agg = this.subsTw.aggregation.type;
267 targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; 267 targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData;
268 this.dataAggregators.forEach((dataAggregator) => { 268 this.dataAggregators.forEach((dataAggregator) => {
269 - dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); 269 + dataAggregator.reset(newSubsTw);
270 }); 270 });
271 } 271 }
  272 + this.subscriber.setTsOffset(this.subsTw.tsOffset);
272 targetCommand.query = this.dataCommand.query; 273 targetCommand.query = this.dataCommand.query;
273 this.subscriber.subscriptionCommands = [targetCommand]; 274 this.subscriber.subscriptionCommands = [targetCommand];
274 } else { 275 } else {
@@ -393,7 +394,7 @@ export class EntityDataSubscription { @@ -393,7 +394,7 @@ export class EntityDataSubscription {
393 if (this.datasourceType === DatasourceType.entity) { 394 if (this.datasourceType === DatasourceType.entity) {
394 this.subsCommand = new EntityDataCmd(); 395 this.subsCommand = new EntityDataCmd();
395 this.subsCommand.cmdId = this.dataCommand.cmdId; 396 this.subsCommand.cmdId = this.dataCommand.cmdId;
396 - this.prepareSubscriptionCommands(this.subsCommand); 397 + this.prepareSubscriptionCommands();
397 if (!this.subsCommand.isEmpty()) { 398 if (!this.subsCommand.isEmpty()) {
398 this.subscriber.subscriptionCommands = [this.subsCommand]; 399 this.subscriber.subscriptionCommands = [this.subsCommand];
399 this.subscriber.update(); 400 this.subscriber.update();
@@ -404,11 +405,11 @@ export class EntityDataSubscription { @@ -404,11 +405,11 @@ export class EntityDataSubscription {
404 this.started = true; 405 this.started = true;
405 } 406 }
406 407
407 - private prepareSubscriptionCommands(cmd: EntityDataCmd) { 408 + private prepareSubscriptionCommands() {
408 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { 409 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) {
409 if (this.tsFields.length > 0) { 410 if (this.tsFields.length > 0) {
410 if (this.history) { 411 if (this.history) {
411 - cmd.historyCmd = { 412 + this.subsCommand.historyCmd = {
412 keys: this.tsFields.map(key => key.key), 413 keys: this.tsFields.map(key => key.key),
413 startTs: this.subsTw.fixedWindow.startTimeMs, 414 startTs: this.subsTw.fixedWindow.startTimeMs,
414 endTs: this.subsTw.fixedWindow.endTimeMs, 415 endTs: this.subsTw.fixedWindow.endTimeMs,
@@ -418,7 +419,7 @@ export class EntityDataSubscription { @@ -418,7 +419,7 @@ export class EntityDataSubscription {
418 fetchLatestPreviousPoint: this.subsTw.aggregation.stateData 419 fetchLatestPreviousPoint: this.subsTw.aggregation.stateData
419 }; 420 };
420 } else { 421 } else {
421 - cmd.tsCmd = { 422 + this.subsCommand.tsCmd = {
422 keys: this.tsFields.map(key => key.key), 423 keys: this.tsFields.map(key => key.key),
423 startTs: this.subsTw.startTs, 424 startTs: this.subsTw.startTs,
424 timeWindow: this.subsTw.aggregation.timeWindow, 425 timeWindow: this.subsTw.aggregation.timeWindow,
@@ -429,9 +430,10 @@ export class EntityDataSubscription { @@ -429,9 +430,10 @@ export class EntityDataSubscription {
429 }; 430 };
430 } 431 }
431 } 432 }
  433 + this.subscriber.setTsOffset(this.subsTw.tsOffset);
432 } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { 434 } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
433 if (this.latestValues.length > 0) { 435 if (this.latestValues.length > 0) {
434 - cmd.latestCmd = { 436 + this.subsCommand.latestCmd = {
435 keys: this.latestValues 437 keys: this.latestValues
436 }; 438 };
437 } 439 }
@@ -745,15 +747,9 @@ export class EntityDataSubscription { @@ -745,15 +747,9 @@ export class EntityDataSubscription {
745 this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); 747 this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb);
746 }, 748 },
747 tsKeyNames, 749 tsKeyNames,
748 - subsTw.startTs,  
749 - subsTw.aggregation.limit,  
750 - subsTw.aggregation.type,  
751 - subsTw.aggregation.timeWindow,  
752 - subsTw.aggregation.interval,  
753 - subsTw.aggregation.stateData, 750 + subsTw,
754 this.utils, 751 this.utils,
755 - this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick,  
756 - subsTw.quickInterval 752 + this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick
757 ); 753 );
758 } 754 }
759 755
@@ -828,7 +824,8 @@ export class EntityDataSubscription { @@ -828,7 +824,8 @@ export class EntityDataSubscription {
828 startTime = dataKey.lastUpdateTime + this.frequency; 824 startTime = dataKey.lastUpdateTime + this.frequency;
829 endTime = dataKey.lastUpdateTime + deltaElapsed; 825 endTime = dataKey.lastUpdateTime + deltaElapsed;
830 } else { 826 } else {
831 - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs; 827 + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs +
  828 + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
832 endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; 829 endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency;
833 if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { 830 if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) {
834 const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; 831 const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit;
@@ -836,8 +833,10 @@ export class EntityDataSubscription { @@ -836,8 +833,10 @@ export class EntityDataSubscription {
836 } 833 }
837 } 834 }
838 } else { 835 } else {
839 - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs;  
840 - endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; 836 + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs +
  837 + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
  838 + endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs +
  839 + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset;
841 } 840 }
842 } 841 }
843 generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); 842 generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime);
@@ -40,7 +40,7 @@ import { @@ -40,7 +40,7 @@ import {
40 calculateIntervalEndTime, 40 calculateIntervalEndTime,
41 calculateIntervalStartTime, 41 calculateIntervalStartTime,
42 createSubscriptionTimewindow, 42 createSubscriptionTimewindow,
43 - createTimewindowForComparison, 43 + createTimewindowForComparison, getCurrentTime,
44 SubscriptionTimewindow, 44 SubscriptionTimewindow,
45 Timewindow, 45 Timewindow,
46 toHistoryTimewindow, 46 toHistoryTimewindow,
@@ -839,9 +839,11 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -839,9 +839,11 @@ export class WidgetSubscription implements IWidgetSubscription {
839 if (this.alarmDataListener) { 839 if (this.alarmDataListener) {
840 this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); 840 this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
841 } 841 }
  842 +
842 if (this.timeWindowConfig) { 843 if (this.timeWindowConfig) {
843 this.updateRealtimeSubscription(); 844 this.updateRealtimeSubscription();
844 } 845 }
  846 +
845 this.alarmDataListener = { 847 this.alarmDataListener = {
846 subscriptionTimewindow: this.subscriptionTimewindow, 848 subscriptionTimewindow: this.subscriptionTimewindow,
847 alarmSource: this.alarmSource, 849 alarmSource: this.alarmSource,
@@ -1082,14 +1084,21 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1082,14 +1084,21 @@ export class WidgetSubscription implements IWidgetSubscription {
1082 1084
1083 private updateTimewindow() { 1085 private updateTimewindow() {
1084 this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; 1086 this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
  1087 + this.timeWindow.timezone = this.subscriptionTimewindow.timezone;
1085 if (this.subscriptionTimewindow.realtimeWindowMs) { 1088 if (this.subscriptionTimewindow.realtimeWindowMs) {
1086 - this.timeWindow.maxTime = calculateIntervalEndTime(  
1087 - this.subscriptionTimewindow.quickInterval, moment().valueOf() + this.timeWindow.stDiff);  
1088 - this.timeWindow.minTime = calculateIntervalStartTime(  
1089 - this.subscriptionTimewindow.quickInterval, this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs); 1089 + if (this.subscriptionTimewindow.quickInterval) {
  1090 + const currentDate = getCurrentTime(this.subscriptionTimewindow.timezone);
  1091 + this.timeWindow.maxTime = calculateIntervalEndTime(
  1092 + this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
  1093 + this.timeWindow.minTime = calculateIntervalStartTime(
  1094 + this.subscriptionTimewindow.quickInterval, currentDate) + this.subscriptionTimewindow.tsOffset;
  1095 + } else {
  1096 + this.timeWindow.maxTime = moment().valueOf() + this.subscriptionTimewindow.tsOffset + this.timeWindow.stDiff;
  1097 + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
  1098 + }
1090 } else if (this.subscriptionTimewindow.fixedWindow) { 1099 } else if (this.subscriptionTimewindow.fixedWindow) {
1091 - this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs;  
1092 - this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; 1100 + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs + this.subscriptionTimewindow.tsOffset;
  1101 + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs + this.subscriptionTimewindow.tsOffset;
1093 } 1102 }
1094 } 1103 }
1095 1104
@@ -1107,12 +1116,13 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1107,12 +1116,13 @@ export class WidgetSubscription implements IWidgetSubscription {
1107 1116
1108 private updateComparisonTimewindow() { 1117 private updateComparisonTimewindow() {
1109 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; 1118 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
  1119 + this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
1110 if (this.timewindowForComparison.realtimeWindowMs) { 1120 if (this.timewindowForComparison.realtimeWindowMs) {
1111 this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); 1121 this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();
1112 this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf(); 1122 this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();
1113 } else if (this.timewindowForComparison.fixedWindow) { 1123 } else if (this.timewindowForComparison.fixedWindow) {
1114 - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs;  
1115 - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; 1124 + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
  1125 + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
1116 } 1126 }
1117 } 1127 }
1118 1128
@@ -1339,7 +1349,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1339,7 +1349,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1339 this.onDataUpdated(); 1349 this.onDataUpdated();
1340 } 1350 }
1341 1351
1342 - private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) { 1352 + private alarmsUpdated(updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
1343 this.alarmsLoaded(alarms, 0, 0); 1353 this.alarmsLoaded(alarms, 0, 0);
1344 } 1354 }
1345 1355
@@ -91,6 +91,7 @@ @@ -91,6 +91,7 @@
91 direction="left" 91 direction="left"
92 tooltipPosition="below" 92 tooltipPosition="below"
93 aggregation="true" 93 aggregation="true"
  94 + timezone="true"
94 [(ngModel)]="dashboardCtx.dashboardTimewindow"> 95 [(ngModel)]="dashboardCtx.dashboardTimewindow">
95 </tb-timewindow> 96 </tb-timewindow>
96 <tb-filters-edit [fxShow]="!isEdit && displayFilters()" 97 <tb-filters-edit [fxShow]="!isEdit && displayFilters()"
@@ -95,6 +95,7 @@ @@ -95,6 +95,7 @@
95 <tb-timewindow *ngIf="widget.hasTimewindow" 95 <tb-timewindow *ngIf="widget.hasTimewindow"
96 #timewindowComponent 96 #timewindowComponent
97 aggregation="{{widget.hasAggregation}}" 97 aggregation="{{widget.hasAggregation}}"
  98 + timezone="true"
98 [isEdit]="isEdit" 99 [isEdit]="isEdit"
99 [(ngModel)]="widgetComponent.widget.config.timewindow" 100 [(ngModel)]="widgetComponent.widget.config.timewindow"
100 (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> 101 (ngModelChange)="widgetComponent.onTimewindowChanged($event)">
@@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; @@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models';
55 import { DialogService } from '@core/services/dialog.service'; 55 import { DialogService } from '@core/services/dialog.service';
56 import { AddEntityDialogComponent } from './add-entity-dialog.component'; 56 import { AddEntityDialogComponent } from './add-entity-dialog.component';
57 import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; 57 import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models';
58 -import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; 58 +import {
  59 + calculateIntervalEndTime,
  60 + calculateIntervalStartTime,
  61 + getCurrentTime,
  62 + HistoryWindowType,
  63 + Timewindow
  64 +} from '@shared/models/time/time.models';
59 import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 65 import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
60 import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; 66 import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
61 import { isDefined, isUndefined } from '@core/utils'; 67 import { isDefined, isUndefined } from '@core/utils';
@@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn @@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
296 const currentTime = Date.now(); 302 const currentTime = Date.now();
297 timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; 303 timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs;
298 timePageLink.endTime = currentTime; 304 timePageLink.endTime = currentTime;
  305 + } else if (this.timewindow.history.historyType === HistoryWindowType.INTERVAL) {
  306 + const currentDate = getCurrentTime();
  307 + timePageLink.startTime = calculateIntervalStartTime(this.timewindow.history.quickInterval, currentDate);
  308 + timePageLink.endTime = calculateIntervalEndTime(this.timewindow.history.quickInterval, currentDate);
299 } else { 309 } else {
300 timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; 310 timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs;
301 timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; 311 timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs;
@@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, @@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator,
94 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) 94 items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems)
95 }); 95 });
96 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { 96 this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => {
97 - getDefaultTimezone().subscribe((defaultTimezone) => {  
98 - this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});  
99 - this.updateValidators(type, true);  
100 - this.alarmScheduleForm.updateValueAndValidity();  
101 - }); 97 + const defaultTimezone = getDefaultTimezone();
  98 + this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false});
  99 + this.updateValidators(type, true);
  100 + this.alarmScheduleForm.updateValueAndValidity();
102 }); 101 });
103 this.alarmScheduleForm.valueChanges.subscribe(() => { 102 this.alarmScheduleForm.valueChanges.subscribe(() => {
104 this.updateModel(); 103 this.updateModel();
@@ -49,7 +49,7 @@ @@ -49,7 +49,7 @@
49 formControlName="quickInterval" 49 formControlName="quickInterval"
50 onlyCurrentInterval="true" 50 onlyCurrentInterval="true"
51 [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" 51 [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
52 - [required]="timewindow.selectedTab === timewindowTypes.HISTORY && 52 + [required]="timewindow.selectedTab === timewindowTypes.REALTIME &&
53 timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" 53 timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL"
54 style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> 54 style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
55 </section> 55 </section>
@@ -99,7 +99,7 @@ @@ -99,7 +99,7 @@
99 formControlName="quickInterval" 99 formControlName="quickInterval"
100 [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" 100 [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
101 [required]="timewindow.selectedTab === timewindowTypes.HISTORY && 101 [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
102 - timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" 102 + timewindowForm.get('history.historyType').value === historyTypes.INTERVAL"
103 style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> 103 style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval>
104 </section> 104 </section>
105 </mat-radio-button> 105 </mat-radio-button>
@@ -177,6 +177,16 @@ @@ -177,6 +177,16 @@
177 predefinedName="aggregation.group-interval"> 177 predefinedName="aggregation.group-interval">
178 </tb-timeinterval> 178 </tb-timeinterval>
179 </div> 179 </div>
  180 + <div *ngIf="timezone" class="mat-content mat-padding" fxLayout="row">
  181 + <section fxLayout="column" [fxShow]="isEdit">
  182 + <label class="tb-small hide-label" translate>timewindow.hide</label>
  183 + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideTimezone"
  184 + (ngModelChange)="onHideTimezoneChanged()"></mat-checkbox>
  185 + </section>
  186 + <tb-timezone-select fxFlex [fxShow]="isEdit || !timewindow.hideTimezone"
  187 + formControlName="timezone">
  188 + </tb-timezone-select>
  189 + </div>
180 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> 190 <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
181 <button type="button" 191 <button type="button"
182 mat-button 192 mat-button
@@ -38,6 +38,7 @@ export interface TimewindowPanelData { @@ -38,6 +38,7 @@ export interface TimewindowPanelData {
38 historyOnly: boolean; 38 historyOnly: boolean;
39 timewindow: Timewindow; 39 timewindow: Timewindow;
40 aggregation: boolean; 40 aggregation: boolean;
  41 + timezone: boolean;
41 isEdit: boolean; 42 isEdit: boolean;
42 } 43 }
43 44
@@ -52,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -52,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
52 53
53 aggregation = false; 54 aggregation = false;
54 55
  56 + timezone = false;
  57 +
55 isEdit = false; 58 isEdit = false;
56 59
57 timewindow: Timewindow; 60 timewindow: Timewindow;
@@ -82,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -82,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
82 this.historyOnly = data.historyOnly; 85 this.historyOnly = data.historyOnly;
83 this.timewindow = data.timewindow; 86 this.timewindow = data.timewindow;
84 this.aggregation = data.aggregation; 87 this.aggregation = data.aggregation;
  88 + this.timezone = data.timezone;
85 this.isEdit = data.isEdit; 89 this.isEdit = data.isEdit;
86 } 90 }
87 91
@@ -89,6 +93,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -89,6 +93,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
89 const hideInterval = this.timewindow.hideInterval || false; 93 const hideInterval = this.timewindow.hideInterval || false;
90 const hideAggregation = this.timewindow.hideAggregation || false; 94 const hideAggregation = this.timewindow.hideAggregation || false;
91 const hideAggInterval = this.timewindow.hideAggInterval || false; 95 const hideAggInterval = this.timewindow.hideAggInterval || false;
  96 + const hideTimezone = this.timewindow.hideTimezone || false;
92 97
93 this.timewindowForm = this.fb.group({ 98 this.timewindowForm = this.fb.group({
94 realtime: this.fb.group( 99 realtime: this.fb.group(
@@ -154,7 +159,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -154,7 +159,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
154 disabled: hideAggInterval 159 disabled: hideAggInterval
155 }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]) 160 }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())])
156 } 161 }
157 - ) 162 + ),
  163 + timezone: this.fb.control({
  164 + value: this.timewindow.timezone !== 'undefined'
  165 + ? this.timewindow.timezone : null,
  166 + disabled: hideTimezone
  167 + })
158 }); 168 });
159 } 169 }
160 170
@@ -171,7 +181,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -171,7 +181,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
171 timewindowMs: timewindowFormValue.history.timewindowMs, 181 timewindowMs: timewindowFormValue.history.timewindowMs,
172 interval: timewindowFormValue.history.interval, 182 interval: timewindowFormValue.history.interval,
173 fixedTimewindow: timewindowFormValue.history.fixedTimewindow, 183 fixedTimewindow: timewindowFormValue.history.fixedTimewindow,
174 - quickInterval: timewindowFormValue.history.quickInterval 184 + quickInterval: timewindowFormValue.history.quickInterval,
175 }; 185 };
176 if (this.aggregation) { 186 if (this.aggregation) {
177 this.timewindow.aggregation = { 187 this.timewindow.aggregation = {
@@ -179,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -179,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
179 limit: timewindowFormValue.aggregation.limit 189 limit: timewindowFormValue.aggregation.limit
180 }; 190 };
181 } 191 }
  192 + if (this.timezone) {
  193 + this.timewindow.timezone = timewindowFormValue.timezone;
  194 + }
182 this.result = this.timewindow; 195 this.result = this.timewindow;
183 this.overlayRef.dispose(); 196 this.overlayRef.dispose();
184 } 197 }
@@ -276,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { @@ -276,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
276 this.timewindowForm.markAsDirty(); 289 this.timewindowForm.markAsDirty();
277 } 290 }
278 291
  292 + onHideTimezoneChanged() {
  293 + if (this.timewindow.hideTimezone) {
  294 + this.timewindowForm.get('timezone').disable({emitEvent: false});
  295 + } else {
  296 + this.timewindowForm.get('timezone').enable({emitEvent: false});
  297 + }
  298 + this.timewindowForm.markAsDirty();
  299 + }
  300 +
279 } 301 }
@@ -91,6 +91,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces @@ -91,6 +91,17 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
91 return this.aggregationValue; 91 return this.aggregationValue;
92 } 92 }
93 93
  94 + timezoneValue = false;
  95 +
  96 + @Input()
  97 + set timezone(val) {
  98 + this.timezoneValue = coerceBooleanProperty(val);
  99 + }
  100 +
  101 + get timezone() {
  102 + return this.timezoneValue;
  103 + }
  104 +
94 isToolbarValue = false; 105 isToolbarValue = false;
95 106
96 @Input() 107 @Input()
@@ -171,7 +182,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces @@ -171,7 +182,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
171 }); 182 });
172 if (isGtXs) { 183 if (isGtXs) {
173 config.minWidth = '417px'; 184 config.minWidth = '417px';
174 - config.maxHeight = '440px'; 185 + config.maxHeight = '500px';
175 const panelHeight = 375; 186 const panelHeight = 375;
176 const panelWidth = 417; 187 const panelWidth = 417;
177 const el = this.timewindowPanelOrigin.elementRef.nativeElement; 188 const el = this.timewindowPanelOrigin.elementRef.nativeElement;
@@ -227,6 +238,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces @@ -227,6 +238,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
227 timewindow: deepClone(this.innerValue), 238 timewindow: deepClone(this.innerValue),
228 historyOnly: this.historyOnly, 239 historyOnly: this.historyOnly,
229 aggregation: this.aggregation, 240 aggregation: this.aggregation,
  241 + timezone: this.timezone,
230 isEdit: this.isEdit 242 isEdit: this.isEdit
231 } 243 }
232 ); 244 );
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 16
17 import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; 17 import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core';
18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; 18 import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
19 -import { Observable } from 'rxjs'; 19 +import { Observable, of } from 'rxjs';
20 import { map, mergeMap, share, tap } from 'rxjs/operators'; 20 import { map, mergeMap, share, tap } from 'rxjs/operators';
21 import { Store } from '@ngrx/store'; 21 import { Store } from '@ngrx/store';
22 import { AppState } from '@app/core/core.state'; 22 import { AppState } from '@app/core/core.state';
@@ -43,10 +43,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -43,10 +43,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
43 43
44 defaultTimezoneId: string = null; 44 defaultTimezoneId: string = null;
45 45
46 - timezones$ = getTimezones().pipe(  
47 - share()  
48 - );  
49 -  
50 @Input() 46 @Input()
51 set defaultTimezone(timezone: string) { 47 set defaultTimezone(timezone: string) {
52 if (this.defaultTimezoneId !== timezone) { 48 if (this.defaultTimezoneId !== timezone) {
@@ -138,23 +134,20 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -138,23 +134,20 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
138 134
139 writeValue(value: string | null): void { 135 writeValue(value: string | null): void {
140 this.searchText = ''; 136 this.searchText = '';
141 - getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(  
142 - (foundTimezone) => {  
143 - if (foundTimezone !== null) {  
144 - this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});  
145 - if (foundTimezone.id !== value) {  
146 - setTimeout(() => {  
147 - this.updateView(foundTimezone.id);  
148 - }, 0);  
149 - } else {  
150 - this.modelValue = value;  
151 - }  
152 - } else {  
153 - this.modelValue = null;  
154 - this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});  
155 - } 137 + const foundTimezone = getTimezoneInfo(value, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
  138 + if (foundTimezone !== null) {
  139 + this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false});
  140 + if (foundTimezone.id !== value) {
  141 + setTimeout(() => {
  142 + this.updateView(foundTimezone.id);
  143 + }, 0);
  144 + } else {
  145 + this.modelValue = value;
156 } 146 }
157 - ); 147 + } else {
  148 + this.modelValue = null;
  149 + this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false});
  150 + }
158 this.dirty = true; 151 this.dirty = true;
159 } 152 }
160 153
@@ -170,14 +163,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -170,14 +163,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
170 this.ignoreClosePanel = false; 163 this.ignoreClosePanel = false;
171 } else { 164 } else {
172 if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) { 165 if (!this.modelValue && (this.defaultTimezoneId || this.userTimezoneByDefaultValue)) {
173 - getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue).subscribe(  
174 - (defaultTimezoneInfo) => {  
175 - if (defaultTimezoneInfo !== null) {  
176 - this.ngZone.run(() => {  
177 - this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});  
178 - });  
179 - }  
180 - }); 166 + const defaultTimezoneInfo = getTimezoneInfo(this.defaultTimezoneId, this.defaultTimezoneId, this.userTimezoneByDefaultValue);
  167 + if (defaultTimezoneInfo !== null) {
  168 + this.ngZone.run(() => {
  169 + this.selectTimezoneFormGroup.get('timezone').reset(defaultTimezoneInfo, {emitEvent: true});
  170 + });
  171 + }
181 } 172 }
182 } 173 }
183 } 174 }
@@ -196,12 +187,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af @@ -196,12 +187,10 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af
196 fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { 187 fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> {
197 this.searchText = searchText; 188 this.searchText = searchText;
198 if (searchText && searchText.length) { 189 if (searchText && searchText.length) {
199 - return getTimezones().pipe(  
200 - map((timezones) => timezones.filter((timezoneInfo) =>  
201 - timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())))  
202 - ); 190 + return of(getTimezones().filter((timezoneInfo) =>
  191 + timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())));
203 } 192 }
204 - return getTimezones(); 193 + return of(getTimezones());
205 } 194 }
206 195
207 clear() { 196 clear() {
@@ -430,6 +430,38 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { @@ -430,6 +430,38 @@ export class EntityDataUpdate extends DataUpdate<EntityData> {
430 constructor(msg: EntityDataUpdateMsg) { 430 constructor(msg: EntityDataUpdateMsg) {
431 super(msg); 431 super(msg);
432 } 432 }
  433 +
  434 + public prepareData(tsOffset: number) {
  435 + if (this.data) {
  436 + this.processEntityData(this.data.data, tsOffset);
  437 + }
  438 + if (this.update) {
  439 + this.processEntityData(this.update, tsOffset);
  440 + }
  441 + }
  442 +
  443 + private processEntityData(data: Array<EntityData>, tsOffset: number) {
  444 + for (const entityData of data) {
  445 + if (entityData.timeseries) {
  446 + for (const key of Object.keys(entityData.timeseries)) {
  447 + const tsValues = entityData.timeseries[key];
  448 + for (const tsValue of tsValues) {
  449 + tsValue.ts += tsOffset;
  450 + }
  451 + }
  452 + }
  453 + if (entityData.latest) {
  454 + for (const entityKeyType of Object.keys(entityData.latest)) {
  455 + const keyTypeValues = entityData.latest[entityKeyType];
  456 + for (const key of Object.keys(keyTypeValues)) {
  457 + const tsValue = keyTypeValues[key];
  458 + tsValue.ts += tsOffset;
  459 + }
  460 + }
  461 + }
  462 + }
  463 + }
  464 +
433 } 465 }
434 466
435 export class AlarmDataUpdate extends DataUpdate<AlarmData> { 467 export class AlarmDataUpdate extends DataUpdate<AlarmData> {
@@ -468,6 +500,8 @@ export class TelemetrySubscriber { @@ -468,6 +500,8 @@ export class TelemetrySubscriber {
468 500
469 private zone: NgZone; 501 private zone: NgZone;
470 502
  503 + private tsOffset = 0;
  504 +
471 public subscriptionCommands: Array<WebsocketCmd>; 505 public subscriptionCommands: Array<WebsocketCmd>;
472 506
473 public data$ = this.dataSubject.asObservable(); 507 public data$ = this.dataSubject.asObservable();
@@ -522,6 +556,10 @@ export class TelemetrySubscriber { @@ -522,6 +556,10 @@ export class TelemetrySubscriber {
522 this.reconnectSubject.complete(); 556 this.reconnectSubject.complete();
523 } 557 }
524 558
  559 + public setTsOffset(tsOffset: number) {
  560 + this.tsOffset = tsOffset;
  561 + }
  562 +
525 public onData(message: SubscriptionUpdate) { 563 public onData(message: SubscriptionUpdate) {
526 const cmdId = message.subscriptionId; 564 const cmdId = message.subscriptionId;
527 let keys: string[]; 565 let keys: string[];
@@ -545,6 +583,9 @@ export class TelemetrySubscriber { @@ -545,6 +583,9 @@ export class TelemetrySubscriber {
545 } 583 }
546 584
547 public onEntityData(message: EntityDataUpdate) { 585 public onEntityData(message: EntityDataUpdate) {
  586 + if (this.tsOffset) {
  587 + message.prepareData(this.tsOffset);
  588 + }
548 if (this.zone) { 589 if (this.zone) {
549 this.zone.run( 590 this.zone.run(
550 () => { 591 () => {
@@ -15,11 +15,9 @@ @@ -15,11 +15,9 @@
15 /// 15 ///
16 16
17 import { TimeService } from '@core/services/time.service'; 17 import { TimeService } from '@core/services/time.service';
18 -import { deepClone, isDefined, isDefinedAndNotNull, isUndefined } from '@app/core/utils'; 18 +import { deepClone, isDefined, isUndefined } from '@app/core/utils';
19 import * as moment_ from 'moment'; 19 import * as moment_ from 'moment';
20 -import { Observable } from 'rxjs/internal/Observable';  
21 -import { from, of } from 'rxjs';  
22 -import { map, mergeMap, tap } from 'rxjs/operators'; 20 +import * as monentTz from 'moment-timezone';
23 21
24 const moment = moment_; 22 const moment = moment_;
25 23
@@ -97,10 +95,12 @@ export interface Timewindow { @@ -97,10 +95,12 @@ export interface Timewindow {
97 hideInterval?: boolean; 95 hideInterval?: boolean;
98 hideAggregation?: boolean; 96 hideAggregation?: boolean;
99 hideAggInterval?: boolean; 97 hideAggInterval?: boolean;
  98 + hideTimezone?: boolean;
100 selectedTab?: TimewindowType; 99 selectedTab?: TimewindowType;
101 realtime?: RealtimeWindow; 100 realtime?: RealtimeWindow;
102 history?: HistoryWindow; 101 history?: HistoryWindow;
103 aggregation?: Aggregation; 102 aggregation?: Aggregation;
  103 + timezone?: string;
104 } 104 }
105 105
106 export interface SubscriptionAggregation extends Aggregation { 106 export interface SubscriptionAggregation extends Aggregation {
@@ -112,6 +112,8 @@ export interface SubscriptionAggregation extends Aggregation { @@ -112,6 +112,8 @@ export interface SubscriptionAggregation extends Aggregation {
112 export interface SubscriptionTimewindow { 112 export interface SubscriptionTimewindow {
113 startTs?: number; 113 startTs?: number;
114 quickInterval?: QuickTimeInterval; 114 quickInterval?: QuickTimeInterval;
  115 + timezone?: string;
  116 + tsOffset?: number;
115 realtimeWindowMs?: number; 117 realtimeWindowMs?: number;
116 fixedWindow?: FixedWindow; 118 fixedWindow?: FixedWindow;
117 aggregation?: SubscriptionAggregation; 119 aggregation?: SubscriptionAggregation;
@@ -121,6 +123,7 @@ export interface WidgetTimewindow { @@ -121,6 +123,7 @@ export interface WidgetTimewindow {
121 minTime?: number; 123 minTime?: number;
122 maxTime?: number; 124 maxTime?: number;
123 interval?: number; 125 interval?: number;
  126 + timezone?: string;
124 stDiff?: number; 127 stDiff?: number;
125 } 128 }
126 129
@@ -178,6 +181,7 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { @@ -178,6 +181,7 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
178 hideInterval: false, 181 hideInterval: false,
179 hideAggregation: false, 182 hideAggregation: false,
180 hideAggInterval: false, 183 hideAggInterval: false,
  184 + hideTimezone: false,
181 selectedTab: TimewindowType.REALTIME, 185 selectedTab: TimewindowType.REALTIME,
182 realtime: { 186 realtime: {
183 realtimeType: RealtimeWindowType.LAST_INTERVAL, 187 realtimeType: RealtimeWindowType.LAST_INTERVAL,
@@ -209,6 +213,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T @@ -209,6 +213,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
209 model.hideInterval = value.hideInterval; 213 model.hideInterval = value.hideInterval;
210 model.hideAggregation = value.hideAggregation; 214 model.hideAggregation = value.hideAggregation;
211 model.hideAggInterval = value.hideAggInterval; 215 model.hideAggInterval = value.hideAggInterval;
  216 + model.hideTimezone = value.hideTimezone;
212 if (isUndefined(value.selectedTab)) { 217 if (isUndefined(value.selectedTab)) {
213 if (value.realtime) { 218 if (value.realtime) {
214 model.selectedTab = TimewindowType.REALTIME; 219 model.selectedTab = TimewindowType.REALTIME;
@@ -266,6 +271,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T @@ -266,6 +271,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
266 } 271 }
267 model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); 272 model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2);
268 } 273 }
  274 + model.timezone = value.timezone;
269 } 275 }
270 return model; 276 return model;
271 } 277 }
@@ -292,6 +298,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -292,6 +298,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
292 hideInterval: timewindow.hideInterval || false, 298 hideInterval: timewindow.hideInterval || false,
293 hideAggregation: timewindow.hideAggregation || false, 299 hideAggregation: timewindow.hideAggregation || false,
294 hideAggInterval: timewindow.hideAggInterval || false, 300 hideAggInterval: timewindow.hideAggInterval || false,
  301 + hideTimezone: timewindow.hideTimezone || false,
295 selectedTab: TimewindowType.HISTORY, 302 selectedTab: TimewindowType.HISTORY,
296 history: { 303 history: {
297 historyType: HistoryWindowType.FIXED, 304 historyType: HistoryWindowType.FIXED,
@@ -304,7 +311,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, @@ -304,7 +311,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
304 aggregation: { 311 aggregation: {
305 type: aggType, 312 type: aggType,
306 limit 313 limit
307 - } 314 + },
  315 + timezone: timewindow.timezone
308 }; 316 };
309 return historyTimewindow; 317 return historyTimewindow;
310 } 318 }
@@ -318,8 +326,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -318,8 +326,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
318 interval: SECOND, 326 interval: SECOND,
319 limit: timeService.getMaxDatapointsLimit(), 327 limit: timeService.getMaxDatapointsLimit(),
320 type: AggregationType.AVG 328 type: AggregationType.AVG
321 - } 329 + },
  330 + timezone: timewindow.timezone,
  331 + tsOffset: 0
322 }; 332 };
  333 + if (timewindow.timezone) {
  334 + const tz = getTimezone(timewindow.timezone);
  335 + const localOffset = moment().utcOffset();
  336 + subscriptionTimewindow.tsOffset = (tz.utcOffset() - localOffset) * 60 * 1000;
  337 + }
323 let aggTimewindow = 0; 338 let aggTimewindow = 0;
324 if (stateData) { 339 if (stateData) {
325 subscriptionTimewindow.aggregation.type = AggregationType.NONE; 340 subscriptionTimewindow.aggregation.type = AggregationType.NONE;
@@ -345,20 +360,25 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -345,20 +360,25 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
345 } 360 }
346 } 361 }
347 if (realtimeType === RealtimeWindowType.INTERVAL) { 362 if (realtimeType === RealtimeWindowType.INTERVAL) {
348 - subscriptionTimewindow.realtimeWindowMs = getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval); 363 + const currentDate = getCurrentTime(timewindow.timezone);
  364 + subscriptionTimewindow.realtimeWindowMs =
  365 + getSubscriptionRealtimeWindowFromTimeInterval(timewindow.realtime.quickInterval, currentDate);
349 subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; 366 subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval;
  367 + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate);
350 } else { 368 } else {
351 subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; 369 subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
  370 + subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;
352 } 371 }
353 subscriptionTimewindow.aggregation.interval = 372 subscriptionTimewindow.aggregation.interval =
354 timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, 373 timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
355 subscriptionTimewindow.aggregation.type); 374 subscriptionTimewindow.aggregation.type);
356 - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs;  
357 - const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;  
358 aggTimewindow = subscriptionTimewindow.realtimeWindowMs; 375 aggTimewindow = subscriptionTimewindow.realtimeWindowMs;
359 - if (startDiff && realtimeType !== RealtimeWindowType.INTERVAL) {  
360 - subscriptionTimewindow.startTs -= startDiff;  
361 - aggTimewindow += subscriptionTimewindow.aggregation.interval; 376 + if (realtimeType !== RealtimeWindowType.INTERVAL) {
  377 + const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
  378 + if (startDiff) {
  379 + subscriptionTimewindow.startTs -= startDiff;
  380 + aggTimewindow += subscriptionTimewindow.aggregation.interval;
  381 + }
362 } 382 }
363 } else { 383 } else {
364 let historyType = timewindow.history.historyType; 384 let historyType = timewindow.history.historyType;
@@ -372,23 +392,24 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -372,23 +392,24 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
372 } 392 }
373 } 393 }
374 if (historyType === HistoryWindowType.LAST_INTERVAL) { 394 if (historyType === HistoryWindowType.LAST_INTERVAL) {
375 - const currentTime = Date.now(); 395 + const currentDate = getCurrentTime(timewindow.timezone);
  396 + const currentTime = currentDate.valueOf();
376 subscriptionTimewindow.fixedWindow = { 397 subscriptionTimewindow.fixedWindow = {
377 startTimeMs: currentTime - timewindow.history.timewindowMs, 398 startTimeMs: currentTime - timewindow.history.timewindowMs,
378 endTimeMs: currentTime 399 endTimeMs: currentTime
379 }; 400 };
380 aggTimewindow = timewindow.history.timewindowMs; 401 aggTimewindow = timewindow.history.timewindowMs;
381 } else if (historyType === HistoryWindowType.INTERVAL) { 402 } else if (historyType === HistoryWindowType.INTERVAL) {
382 - const currentDate = moment(); 403 + const currentDate = getCurrentTime(timewindow.timezone);
383 subscriptionTimewindow.fixedWindow = { 404 subscriptionTimewindow.fixedWindow = {
384 - startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, null, currentDate),  
385 - endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, null, currentDate) 405 + startTimeMs: calculateIntervalStartTime(timewindow.history.quickInterval, currentDate),
  406 + endTimeMs: calculateIntervalEndTime(timewindow.history.quickInterval, currentDate)
386 }; 407 };
387 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; 408 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
388 } else { 409 } else {
389 subscriptionTimewindow.fixedWindow = { 410 subscriptionTimewindow.fixedWindow = {
390 - startTimeMs: timewindow.history.fixedTimewindow.startTimeMs,  
391 - endTimeMs: timewindow.history.fixedTimewindow.endTimeMs 411 + startTimeMs: timewindow.history.fixedTimewindow.startTimeMs - subscriptionTimewindow.tsOffset,
  412 + endTimeMs: timewindow.history.fixedTimewindow.endTimeMs - subscriptionTimewindow.tsOffset
392 }; 413 };
393 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; 414 aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
394 } 415 }
@@ -404,88 +425,85 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num @@ -404,88 +425,85 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
404 return subscriptionTimewindow; 425 return subscriptionTimewindow;
405 } 426 }
406 427
407 -function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval): number {  
408 - const currentDate = moment(); 428 +function getSubscriptionRealtimeWindowFromTimeInterval(interval: QuickTimeInterval, currentDate: moment_.Moment): number {
409 switch (interval) { 429 switch (interval) {
410 case QuickTimeInterval.CURRENT_HOUR: 430 case QuickTimeInterval.CURRENT_HOUR:
411 - return currentDate.diff(currentDate.clone().startOf('hour')) 431 + return HOUR;
412 case QuickTimeInterval.CURRENT_DAY: 432 case QuickTimeInterval.CURRENT_DAY:
413 case QuickTimeInterval.CURRENT_DAY_SO_FAR: 433 case QuickTimeInterval.CURRENT_DAY_SO_FAR:
414 - return currentDate.diff(currentDate.clone().startOf('day')); 434 + return DAY;
415 case QuickTimeInterval.CURRENT_WEEK: 435 case QuickTimeInterval.CURRENT_WEEK:
416 case QuickTimeInterval.CURRENT_WEEK_SO_FAR: 436 case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
417 - return currentDate.diff(currentDate.clone().startOf('week')); 437 + return WEEK;
418 case QuickTimeInterval.CURRENT_MONTH: 438 case QuickTimeInterval.CURRENT_MONTH:
419 case QuickTimeInterval.CURRENT_MONTH_SO_FAR: 439 case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
420 - return currentDate.diff(currentDate.clone().startOf('month')); 440 + return currentDate.endOf('month').diff(currentDate.clone().startOf('month'));
421 case QuickTimeInterval.CURRENT_YEAR: 441 case QuickTimeInterval.CURRENT_YEAR:
422 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 442 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
423 - return currentDate.diff(currentDate.clone().startOf('year')); 443 + return currentDate.endOf('year').diff(currentDate.clone().startOf('year'));
424 } 444 }
425 } 445 }
426 446
427 -export function calculateIntervalEndTime(interval: QuickTimeInterval, endTs = 0, nowDate?: moment_.Moment): number {  
428 - const currentDate = isDefinedAndNotNull(nowDate) ? nowDate.clone() : moment();  
429 - switch (interval) {  
430 - case QuickTimeInterval.YESTERDAY:  
431 - currentDate.subtract(1, 'days');  
432 - return currentDate.endOf('day').valueOf();  
433 - case QuickTimeInterval.DAY_BEFORE_YESTERDAY:  
434 - currentDate.subtract(2, 'days');  
435 - return currentDate.endOf('day').valueOf();  
436 - case QuickTimeInterval.THIS_DAY_LAST_WEEK:  
437 - currentDate.subtract(1, 'weeks');  
438 - return currentDate.endOf('day').valueOf();  
439 - case QuickTimeInterval.PREVIOUS_WEEK:  
440 - currentDate.subtract(1, 'weeks');  
441 - return currentDate.endOf('week').valueOf();  
442 - case QuickTimeInterval.PREVIOUS_MONTH:  
443 - currentDate.subtract(1, 'months');  
444 - return currentDate.endOf('month').valueOf();  
445 - case QuickTimeInterval.PREVIOUS_YEAR:  
446 - currentDate.subtract(1, 'years');  
447 - return currentDate.endOf('year').valueOf();  
448 - case QuickTimeInterval.CURRENT_HOUR:  
449 - return currentDate.endOf('hour').valueOf();  
450 - case QuickTimeInterval.CURRENT_DAY:  
451 - return currentDate.endOf('day').valueOf();  
452 - case QuickTimeInterval.CURRENT_WEEK:  
453 - return currentDate.endOf('week').valueOf();  
454 - case QuickTimeInterval.CURRENT_MONTH:  
455 - return currentDate.endOf('month').valueOf();  
456 - case QuickTimeInterval.CURRENT_YEAR:  
457 - return currentDate.endOf('year').valueOf();  
458 - case QuickTimeInterval.CURRENT_DAY_SO_FAR:  
459 - case QuickTimeInterval.CURRENT_WEEK_SO_FAR:  
460 - case QuickTimeInterval.CURRENT_MONTH_SO_FAR:  
461 - case QuickTimeInterval.CURRENT_YEAR_SO_FAR:  
462 - return currentDate.valueOf();  
463 - default:  
464 - return endTs; 447 +export function calculateIntervalEndTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
  448 + currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
  449 + switch (interval) {
  450 + case QuickTimeInterval.YESTERDAY:
  451 + currentDate.subtract(1, 'days');
  452 + return currentDate.endOf('day').valueOf();
  453 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  454 + currentDate.subtract(2, 'days');
  455 + return currentDate.endOf('day').valueOf();
  456 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  457 + currentDate.subtract(1, 'weeks');
  458 + return currentDate.endOf('day').valueOf();
  459 + case QuickTimeInterval.PREVIOUS_WEEK:
  460 + currentDate.subtract(1, 'weeks');
  461 + return currentDate.endOf('week').valueOf();
  462 + case QuickTimeInterval.PREVIOUS_MONTH:
  463 + currentDate.subtract(1, 'months');
  464 + return currentDate.endOf('month').valueOf();
  465 + case QuickTimeInterval.PREVIOUS_YEAR:
  466 + currentDate.subtract(1, 'years');
  467 + return currentDate.endOf('year').valueOf();
  468 + case QuickTimeInterval.CURRENT_HOUR:
  469 + return currentDate.endOf('hour').valueOf();
  470 + case QuickTimeInterval.CURRENT_DAY:
  471 + return currentDate.endOf('day').valueOf();
  472 + case QuickTimeInterval.CURRENT_WEEK:
  473 + return currentDate.endOf('week').valueOf();
  474 + case QuickTimeInterval.CURRENT_MONTH:
  475 + return currentDate.endOf('month').valueOf();
  476 + case QuickTimeInterval.CURRENT_YEAR:
  477 + return currentDate.endOf('year').valueOf();
  478 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  479 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  480 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  481 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  482 + return currentDate.valueOf();
465 } 483 }
466 } 484 }
467 485
468 -export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS = 0, nowDate?: moment_.Moment): number {  
469 - const currentDate = isDefinedAndNotNull(nowDate) ? nowDate.clone() : moment(); 486 +export function calculateIntervalStartTime(interval: QuickTimeInterval, currentDate: moment_.Moment = null, tz: string = ''): number {
  487 + currentDate = currentDate ? currentDate.clone() : getCurrentTime(tz);
470 switch (interval) { 488 switch (interval) {
471 case QuickTimeInterval.YESTERDAY: 489 case QuickTimeInterval.YESTERDAY:
472 currentDate.subtract(1, 'days'); 490 currentDate.subtract(1, 'days');
473 - return currentDate.startOf('day').valueOf(); 491 + return currentDate.startOf('day').valueOf();
474 case QuickTimeInterval.DAY_BEFORE_YESTERDAY: 492 case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
475 currentDate.subtract(2, 'days'); 493 currentDate.subtract(2, 'days');
476 return currentDate.startOf('day').valueOf(); 494 return currentDate.startOf('day').valueOf();
477 case QuickTimeInterval.THIS_DAY_LAST_WEEK: 495 case QuickTimeInterval.THIS_DAY_LAST_WEEK:
478 currentDate.subtract(1, 'weeks'); 496 currentDate.subtract(1, 'weeks');
479 - return currentDate.startOf('day').valueOf(); 497 + return currentDate.startOf('day').valueOf();
480 case QuickTimeInterval.PREVIOUS_WEEK: 498 case QuickTimeInterval.PREVIOUS_WEEK:
481 currentDate.subtract(1, 'weeks'); 499 currentDate.subtract(1, 'weeks');
482 - return currentDate.startOf('week').valueOf(); 500 + return currentDate.startOf('week').valueOf();
483 case QuickTimeInterval.PREVIOUS_MONTH: 501 case QuickTimeInterval.PREVIOUS_MONTH:
484 currentDate.subtract(1, 'months'); 502 currentDate.subtract(1, 'months');
485 - return currentDate.startOf('month').valueOf(); 503 + return currentDate.startOf('month').valueOf();
486 case QuickTimeInterval.PREVIOUS_YEAR: 504 case QuickTimeInterval.PREVIOUS_YEAR:
487 currentDate.subtract(1, 'years'); 505 currentDate.subtract(1, 'years');
488 - return currentDate.startOf('year').valueOf(); 506 + return currentDate.startOf('year').valueOf();
489 case QuickTimeInterval.CURRENT_HOUR: 507 case QuickTimeInterval.CURRENT_HOUR:
490 return currentDate.startOf('hour').valueOf(); 508 return currentDate.startOf('hour').valueOf();
491 case QuickTimeInterval.CURRENT_DAY: 509 case QuickTimeInterval.CURRENT_DAY:
@@ -500,8 +518,6 @@ export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS @@ -500,8 +518,6 @@ export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS
500 case QuickTimeInterval.CURRENT_YEAR: 518 case QuickTimeInterval.CURRENT_YEAR:
501 case QuickTimeInterval.CURRENT_YEAR_SO_FAR: 519 case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
502 return currentDate.startOf('year').valueOf(); 520 return currentDate.startOf('year').valueOf();
503 - default:  
504 - return startTS;  
505 } 521 }
506 } 522 }
507 523
@@ -560,6 +576,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { @@ -560,6 +576,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
560 cloned.hideInterval = timewindow.hideInterval || false; 576 cloned.hideInterval = timewindow.hideInterval || false;
561 cloned.hideAggregation = timewindow.hideAggregation || false; 577 cloned.hideAggregation = timewindow.hideAggregation || false;
562 cloned.hideAggInterval = timewindow.hideAggInterval || false; 578 cloned.hideAggInterval = timewindow.hideAggInterval || false;
  579 + cloned.hideTimezone = timewindow.hideTimezone || false;
563 if (isDefined(timewindow.selectedTab)) { 580 if (isDefined(timewindow.selectedTab)) {
564 cloned.selectedTab = timewindow.selectedTab; 581 cloned.selectedTab = timewindow.selectedTab;
565 if (timewindow.selectedTab === TimewindowType.REALTIME) { 582 if (timewindow.selectedTab === TimewindowType.REALTIME) {
@@ -569,6 +586,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { @@ -569,6 +586,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
569 } 586 }
570 } 587 }
571 cloned.aggregation = deepClone(timewindow.aggregation); 588 cloned.aggregation = deepClone(timewindow.aggregation);
  589 + cloned.timezone = timewindow.timezone;
572 return cloned; 590 return cloned;
573 } 591 }
574 592
@@ -718,60 +736,50 @@ export interface TimezoneInfo { @@ -718,60 +736,50 @@ export interface TimezoneInfo {
718 let timezones: TimezoneInfo[] = null; 736 let timezones: TimezoneInfo[] = null;
719 let defaultTimezone: string = null; 737 let defaultTimezone: string = null;
720 738
721 -export function getTimezones(): Observable<TimezoneInfo[]> {  
722 - if (timezones) {  
723 - return of(timezones);  
724 - } else {  
725 - return from(import('moment-timezone')).pipe(  
726 - map((monentTz) => {  
727 - return monentTz.tz.names().map((zoneName) => {  
728 - const tz = monentTz.tz(zoneName);  
729 - return {  
730 - id: zoneName,  
731 - name: zoneName.replace(/_/g, ' '),  
732 - offset: `UTC${tz.format('Z')}`,  
733 - nOffset: tz.utcOffset()  
734 - };  
735 - });  
736 - }),  
737 - tap((zones) => {  
738 - timezones = zones;  
739 - })  
740 - ); 739 +export function getTimezones(): TimezoneInfo[] {
  740 + if (!timezones) {
  741 + timezones = monentTz.tz.names().map((zoneName) => {
  742 + const tz = monentTz.tz(zoneName);
  743 + return {
  744 + id: zoneName,
  745 + name: zoneName.replace(/_/g, ' '),
  746 + offset: `UTC${tz.format('Z')}`,
  747 + nOffset: tz.utcOffset()
  748 + };
  749 + });
  750 + }
  751 + return timezones;
  752 +}
  753 +
  754 +export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): TimezoneInfo {
  755 + const timezoneList = getTimezones();
  756 + let foundTimezone = timezoneId ? timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId) : null;
  757 + if (!foundTimezone) {
  758 + if (userTimezoneByDefault) {
  759 + const userTimezone = getDefaultTimezone();
  760 + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);
  761 + } else if (defaultTimezoneId) {
  762 + foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);
  763 + }
741 } 764 }
  765 + return foundTimezone;
742 } 766 }
743 767
744 -export function getTimezoneInfo(timezoneId: string, defaultTimezoneId?: string, userTimezoneByDefault?: boolean): Observable<TimezoneInfo> {  
745 - return getTimezones().pipe(  
746 - mergeMap((timezoneList) => {  
747 - let foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === timezoneId);  
748 - if (!foundTimezone) {  
749 - if (userTimezoneByDefault) {  
750 - return getDefaultTimezone().pipe(  
751 - map((userTimezone) => {  
752 - return timezoneList.find(timezoneInfo => timezoneInfo.id === userTimezone);  
753 - })  
754 - );  
755 - } else if (defaultTimezoneId) {  
756 - foundTimezone = timezoneList.find(timezoneInfo => timezoneInfo.id === defaultTimezoneId);  
757 - }  
758 - }  
759 - return of(foundTimezone);  
760 - })  
761 - ); 768 +export function getDefaultTimezone(): string {
  769 + if (!defaultTimezone) {
  770 + defaultTimezone = monentTz.tz.guess();
  771 + }
  772 + return defaultTimezone;
762 } 773 }
763 774
764 -export function getDefaultTimezone(): Observable<string> {  
765 - if (defaultTimezone) {  
766 - return of(defaultTimezone); 775 +export function getCurrentTime(tz?: string): moment_.Moment {
  776 + if (tz) {
  777 + return moment().tz(tz);
767 } else { 778 } else {
768 - return from(import('moment-timezone')).pipe(  
769 - map((monentTz) => {  
770 - return monentTz.tz.guess();  
771 - }),  
772 - tap((zone) => {  
773 - defaultTimezone = zone;  
774 - })  
775 - ); 779 + return moment();
776 } 780 }
777 } 781 }
  782 +
  783 +export function getTimezone(tz: string): moment_.Moment {
  784 + return moment.tz(tz);
  785 +}