Commit 3ed49e3f026437973515a48221c1a71a85e16db7
1 parent
d184d8db
Add timezone support for timewindow
Showing
13 changed files
with
340 additions
and
233 deletions
... | ... | @@ -18,8 +18,8 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/tel |
18 | 18 | import { |
19 | 19 | AggregationType, |
20 | 20 | calculateIntervalEndTime, |
21 | - calculateIntervalStartTime, | |
22 | - QuickTimeInterval | |
21 | + calculateIntervalStartTime, getCurrentTime, | |
22 | + QuickTimeInterval, SubscriptionTimewindow | |
23 | 23 | } from '@shared/models/time/time.models'; |
24 | 24 | import { UtilsService } from '@core/services/utils.service'; |
25 | 25 | import { deepClone } from '@core/utils'; |
... | ... | @@ -78,34 +78,29 @@ export class DataAggregator { |
78 | 78 | private resetPending = false; |
79 | 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 | 83 | private readonly aggFunction: AggFunction; |
84 | 84 | |
85 | 85 | private intervalTimeoutHandle: Timeout; |
86 | 86 | private intervalScheduledTime: number; |
87 | 87 | |
88 | + private startTs = this.subsTw.startTs + this.subsTw.tsOffset; | |
88 | 89 | private endTs: number; |
89 | 90 | private elapsed: number; |
90 | 91 | |
91 | 92 | constructor(private onDataCb: onAggregatedData, |
92 | 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 | 95 | private utils: UtilsService, |
100 | - private ignoreDataUpdateOnIntervalTick: boolean, | |
101 | - private quickInterval: QuickTimeInterval) { | |
96 | + private ignoreDataUpdateOnIntervalTick: boolean) { | |
102 | 97 | this.tsKeyNames.forEach((key) => { |
103 | 98 | this.dataBuffer[key] = []; |
104 | 99 | }); |
105 | - if (this.stateData) { | |
100 | + if (this.subsTw.aggregation.stateData) { | |
106 | 101 | this.lastPrevKvPairData = {}; |
107 | 102 | } |
108 | - switch (this.aggregationType) { | |
103 | + switch (this.subsTw.aggregation.type) { | |
109 | 104 | case AggregationType.MIN: |
110 | 105 | this.aggFunction = min; |
111 | 106 | break; |
... | ... | @@ -135,19 +130,21 @@ export class DataAggregator { |
135 | 130 | return prevOnDataCb; |
136 | 131 | } |
137 | 132 | |
138 | - public reset(startTs: number, timeWindow: number, interval: number) { | |
133 | + public reset(subsTw: SubscriptionTimewindow) { | |
139 | 134 | if (this.intervalTimeoutHandle) { |
140 | 135 | clearTimeout(this.intervalTimeoutHandle); |
141 | 136 | this.intervalTimeoutHandle = null; |
142 | 137 | } |
138 | + this.subsTw = subsTw; | |
143 | 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 | 146 | this.elapsed = 0; |
150 | - this.aggregationTimeout = Math.max(this.interval, 1000); | |
147 | + this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); | |
151 | 148 | this.resetPending = true; |
152 | 149 | this.updatedData = false; |
153 | 150 | this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); |
... | ... | @@ -168,8 +165,11 @@ export class DataAggregator { |
168 | 165 | if (!this.dataReceived) { |
169 | 166 | this.elapsed = 0; |
170 | 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 | 174 | if (this.resetPending) { |
175 | 175 | this.resetPending = false; |
... | ... | @@ -203,15 +203,19 @@ export class DataAggregator { |
203 | 203 | this.intervalTimeoutHandle = null; |
204 | 204 | } |
205 | 205 | if (!history) { |
206 | - const delta = Math.floor(this.elapsed / this.interval); | |
206 | + const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); | |
207 | 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 | 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 | 220 | } else { |
217 | 221 | this.data = this.updateData(); |
... | ... | @@ -234,7 +238,7 @@ export class DataAggregator { |
234 | 238 | let keyData = this.dataBuffer[key]; |
235 | 239 | aggKeyData.forEach((aggData, aggTimestamp) => { |
236 | 240 | if (aggTimestamp <= this.startTs) { |
237 | - if (this.stateData && | |
241 | + if (this.subsTw.aggregation.stateData && | |
238 | 242 | (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { |
239 | 243 | this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; |
240 | 244 | } |
... | ... | @@ -246,11 +250,11 @@ export class DataAggregator { |
246 | 250 | } |
247 | 251 | }); |
248 | 252 | keyData.sort((set1, set2) => set1[0] - set2[0]); |
249 | - if (this.stateData) { | |
253 | + if (this.subsTw.aggregation.stateData) { | |
250 | 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 | 259 | this.dataBuffer[key] = keyData; |
256 | 260 | } |
... | ... | @@ -286,7 +290,7 @@ export class DataAggregator { |
286 | 290 | } |
287 | 291 | |
288 | 292 | private processAggregatedData(data: SubscriptionData): AggregationMap { |
289 | - const isCount = this.aggregationType === AggregationType.COUNT; | |
293 | + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; | |
290 | 294 | const aggregationMap: AggregationMap = {}; |
291 | 295 | for (const key of Object.keys(data)) { |
292 | 296 | let aggKeyData = aggregationMap[key]; |
... | ... | @@ -311,7 +315,7 @@ export class DataAggregator { |
311 | 315 | } |
312 | 316 | |
313 | 317 | private updateAggregatedData(data: SubscriptionData) { |
314 | - const isCount = this.aggregationType === AggregationType.COUNT; | |
318 | + const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; | |
315 | 319 | for (const key of Object.keys(data)) { |
316 | 320 | let aggKeyData = this.aggregationMap[key]; |
317 | 321 | if (!aggKeyData) { |
... | ... | @@ -323,7 +327,8 @@ export class DataAggregator { |
323 | 327 | const timestamp = kvPair[0]; |
324 | 328 | const value = this.convertValue(kvPair[1]); |
325 | 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 | 332 | let aggData = aggKeyData.get(aggTimestamp); |
328 | 333 | if (!aggData) { |
329 | 334 | aggData = { | ... | ... |
... | ... | @@ -237,7 +237,7 @@ export class EntityDataSubscription { |
237 | 237 | }; |
238 | 238 | |
239 | 239 | if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { |
240 | - this.prepareSubscriptionCommands(this.dataCommand); | |
240 | + this.prepareSubscriptionCommands(); | |
241 | 241 | } |
242 | 242 | |
243 | 243 | this.subscriber.subscriptionCommands.push(this.dataCommand); |
... | ... | @@ -256,8 +256,8 @@ export class EntityDataSubscription { |
256 | 256 | if (this.started) { |
257 | 257 | const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; |
258 | 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 | 261 | this.subsTw = newSubsTw; |
262 | 262 | targetCommand.tsCmd.startTs = this.subsTw.startTs; |
263 | 263 | targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; |
... | ... | @@ -266,9 +266,10 @@ export class EntityDataSubscription { |
266 | 266 | targetCommand.tsCmd.agg = this.subsTw.aggregation.type; |
267 | 267 | targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; |
268 | 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 | 273 | targetCommand.query = this.dataCommand.query; |
273 | 274 | this.subscriber.subscriptionCommands = [targetCommand]; |
274 | 275 | } else { |
... | ... | @@ -393,7 +394,7 @@ export class EntityDataSubscription { |
393 | 394 | if (this.datasourceType === DatasourceType.entity) { |
394 | 395 | this.subsCommand = new EntityDataCmd(); |
395 | 396 | this.subsCommand.cmdId = this.dataCommand.cmdId; |
396 | - this.prepareSubscriptionCommands(this.subsCommand); | |
397 | + this.prepareSubscriptionCommands(); | |
397 | 398 | if (!this.subsCommand.isEmpty()) { |
398 | 399 | this.subscriber.subscriptionCommands = [this.subsCommand]; |
399 | 400 | this.subscriber.update(); |
... | ... | @@ -404,11 +405,11 @@ export class EntityDataSubscription { |
404 | 405 | this.started = true; |
405 | 406 | } |
406 | 407 | |
407 | - private prepareSubscriptionCommands(cmd: EntityDataCmd) { | |
408 | + private prepareSubscriptionCommands() { | |
408 | 409 | if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { |
409 | 410 | if (this.tsFields.length > 0) { |
410 | 411 | if (this.history) { |
411 | - cmd.historyCmd = { | |
412 | + this.subsCommand.historyCmd = { | |
412 | 413 | keys: this.tsFields.map(key => key.key), |
413 | 414 | startTs: this.subsTw.fixedWindow.startTimeMs, |
414 | 415 | endTs: this.subsTw.fixedWindow.endTimeMs, |
... | ... | @@ -418,7 +419,7 @@ export class EntityDataSubscription { |
418 | 419 | fetchLatestPreviousPoint: this.subsTw.aggregation.stateData |
419 | 420 | }; |
420 | 421 | } else { |
421 | - cmd.tsCmd = { | |
422 | + this.subsCommand.tsCmd = { | |
422 | 423 | keys: this.tsFields.map(key => key.key), |
423 | 424 | startTs: this.subsTw.startTs, |
424 | 425 | timeWindow: this.subsTw.aggregation.timeWindow, |
... | ... | @@ -429,9 +430,10 @@ export class EntityDataSubscription { |
429 | 430 | }; |
430 | 431 | } |
431 | 432 | } |
433 | + this.subscriber.setTsOffset(this.subsTw.tsOffset); | |
432 | 434 | } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { |
433 | 435 | if (this.latestValues.length > 0) { |
434 | - cmd.latestCmd = { | |
436 | + this.subsCommand.latestCmd = { | |
435 | 437 | keys: this.latestValues |
436 | 438 | }; |
437 | 439 | } |
... | ... | @@ -745,15 +747,9 @@ export class EntityDataSubscription { |
745 | 747 | this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); |
746 | 748 | }, |
747 | 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 | 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 | 824 | startTime = dataKey.lastUpdateTime + this.frequency; |
829 | 825 | endTime = dataKey.lastUpdateTime + deltaElapsed; |
830 | 826 | } else { |
831 | - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs; | |
827 | + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs + | |
828 | + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; | |
832 | 829 | endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; |
833 | 830 | if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { |
834 | 831 | const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; |
... | ... | @@ -836,8 +833,10 @@ export class EntityDataSubscription { |
836 | 833 | } |
837 | 834 | } |
838 | 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 | 842 | generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); | ... | ... |
... | ... | @@ -40,7 +40,7 @@ import { |
40 | 40 | calculateIntervalEndTime, |
41 | 41 | calculateIntervalStartTime, |
42 | 42 | createSubscriptionTimewindow, |
43 | - createTimewindowForComparison, | |
43 | + createTimewindowForComparison, getCurrentTime, | |
44 | 44 | SubscriptionTimewindow, |
45 | 45 | Timewindow, |
46 | 46 | toHistoryTimewindow, |
... | ... | @@ -839,9 +839,11 @@ export class WidgetSubscription implements IWidgetSubscription { |
839 | 839 | if (this.alarmDataListener) { |
840 | 840 | this.ctx.alarmDataService.stopSubscription(this.alarmDataListener); |
841 | 841 | } |
842 | + | |
842 | 843 | if (this.timeWindowConfig) { |
843 | 844 | this.updateRealtimeSubscription(); |
844 | 845 | } |
846 | + | |
845 | 847 | this.alarmDataListener = { |
846 | 848 | subscriptionTimewindow: this.subscriptionTimewindow, |
847 | 849 | alarmSource: this.alarmSource, |
... | ... | @@ -1082,14 +1084,21 @@ export class WidgetSubscription implements IWidgetSubscription { |
1082 | 1084 | |
1083 | 1085 | private updateTimewindow() { |
1084 | 1086 | this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; |
1087 | + this.timeWindow.timezone = this.subscriptionTimewindow.timezone; | |
1085 | 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 | 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 | 1116 | |
1108 | 1117 | private updateComparisonTimewindow() { |
1109 | 1118 | this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; |
1119 | + this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone; | |
1110 | 1120 | if (this.timewindowForComparison.realtimeWindowMs) { |
1111 | 1121 | this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); |
1112 | 1122 | this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf(); |
1113 | 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 | 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 | 1353 | this.alarmsLoaded(alarms, 0, 0); |
1344 | 1354 | } |
1345 | 1355 | ... | ... |
... | ... | @@ -95,6 +95,7 @@ |
95 | 95 | <tb-timewindow *ngIf="widget.hasTimewindow" |
96 | 96 | #timewindowComponent |
97 | 97 | aggregation="{{widget.hasAggregation}}" |
98 | + timezone="true" | |
98 | 99 | [isEdit]="isEdit" |
99 | 100 | [(ngModel)]="widgetComponent.widget.config.timewindow" |
100 | 101 | (ngModelChange)="widgetComponent.onTimewindowChanged($event)"> | ... | ... |
... | ... | @@ -55,7 +55,13 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; |
55 | 55 | import { DialogService } from '@core/services/dialog.service'; |
56 | 56 | import { AddEntityDialogComponent } from './add-entity-dialog.component'; |
57 | 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 | 65 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
60 | 66 | import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
61 | 67 | import { isDefined, isUndefined } from '@core/utils'; |
... | ... | @@ -296,6 +302,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
296 | 302 | const currentTime = Date.now(); |
297 | 303 | timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; |
298 | 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 | 309 | } else { |
300 | 310 | timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; |
301 | 311 | timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; | ... | ... |
... | ... | @@ -94,11 +94,10 @@ export class AlarmScheduleComponent implements ControlValueAccessor, Validator, |
94 | 94 | items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems) |
95 | 95 | }); |
96 | 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 | 102 | this.alarmScheduleForm.valueChanges.subscribe(() => { |
104 | 103 | this.updateModel(); | ... | ... |
... | ... | @@ -49,7 +49,7 @@ |
49 | 49 | formControlName="quickInterval" |
50 | 50 | onlyCurrentInterval="true" |
51 | 51 | [fxShow]="timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" |
52 | - [required]="timewindow.selectedTab === timewindowTypes.HISTORY && | |
52 | + [required]="timewindow.selectedTab === timewindowTypes.REALTIME && | |
53 | 53 | timewindowForm.get('realtime.realtimeType').value === realtimeTypes.INTERVAL" |
54 | 54 | style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> |
55 | 55 | </section> |
... | ... | @@ -99,7 +99,7 @@ |
99 | 99 | formControlName="quickInterval" |
100 | 100 | [fxShow]="timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" |
101 | 101 | [required]="timewindow.selectedTab === timewindowTypes.HISTORY && |
102 | - timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" | |
102 | + timewindowForm.get('history.historyType').value === historyTypes.INTERVAL" | |
103 | 103 | style="padding-top: 8px; min-width: 364px"></tb-quick-time-interval> |
104 | 104 | </section> |
105 | 105 | </mat-radio-button> |
... | ... | @@ -177,6 +177,16 @@ |
177 | 177 | predefinedName="aggregation.group-interval"> |
178 | 178 | </tb-timeinterval> |
179 | 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 | 190 | <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> |
181 | 191 | <button type="button" |
182 | 192 | mat-button | ... | ... |
... | ... | @@ -38,6 +38,7 @@ export interface TimewindowPanelData { |
38 | 38 | historyOnly: boolean; |
39 | 39 | timewindow: Timewindow; |
40 | 40 | aggregation: boolean; |
41 | + timezone: boolean; | |
41 | 42 | isEdit: boolean; |
42 | 43 | } |
43 | 44 | |
... | ... | @@ -52,6 +53,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
52 | 53 | |
53 | 54 | aggregation = false; |
54 | 55 | |
56 | + timezone = false; | |
57 | + | |
55 | 58 | isEdit = false; |
56 | 59 | |
57 | 60 | timewindow: Timewindow; |
... | ... | @@ -82,6 +85,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
82 | 85 | this.historyOnly = data.historyOnly; |
83 | 86 | this.timewindow = data.timewindow; |
84 | 87 | this.aggregation = data.aggregation; |
88 | + this.timezone = data.timezone; | |
85 | 89 | this.isEdit = data.isEdit; |
86 | 90 | } |
87 | 91 | |
... | ... | @@ -89,6 +93,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
89 | 93 | const hideInterval = this.timewindow.hideInterval || false; |
90 | 94 | const hideAggregation = this.timewindow.hideAggregation || false; |
91 | 95 | const hideAggInterval = this.timewindow.hideAggInterval || false; |
96 | + const hideTimezone = this.timewindow.hideTimezone || false; | |
92 | 97 | |
93 | 98 | this.timewindowForm = this.fb.group({ |
94 | 99 | realtime: this.fb.group( |
... | ... | @@ -154,7 +159,12 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
154 | 159 | disabled: hideAggInterval |
155 | 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 | 181 | timewindowMs: timewindowFormValue.history.timewindowMs, |
172 | 182 | interval: timewindowFormValue.history.interval, |
173 | 183 | fixedTimewindow: timewindowFormValue.history.fixedTimewindow, |
174 | - quickInterval: timewindowFormValue.history.quickInterval | |
184 | + quickInterval: timewindowFormValue.history.quickInterval, | |
175 | 185 | }; |
176 | 186 | if (this.aggregation) { |
177 | 187 | this.timewindow.aggregation = { |
... | ... | @@ -179,6 +189,9 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
179 | 189 | limit: timewindowFormValue.aggregation.limit |
180 | 190 | }; |
181 | 191 | } |
192 | + if (this.timezone) { | |
193 | + this.timewindow.timezone = timewindowFormValue.timezone; | |
194 | + } | |
182 | 195 | this.result = this.timewindow; |
183 | 196 | this.overlayRef.dispose(); |
184 | 197 | } |
... | ... | @@ -276,4 +289,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { |
276 | 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 | 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 | 105 | isToolbarValue = false; |
95 | 106 | |
96 | 107 | @Input() |
... | ... | @@ -171,7 +182,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces |
171 | 182 | }); |
172 | 183 | if (isGtXs) { |
173 | 184 | config.minWidth = '417px'; |
174 | - config.maxHeight = '440px'; | |
185 | + config.maxHeight = '500px'; | |
175 | 186 | const panelHeight = 375; |
176 | 187 | const panelWidth = 417; |
177 | 188 | const el = this.timewindowPanelOrigin.elementRef.nativeElement; |
... | ... | @@ -227,6 +238,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces |
227 | 238 | timewindow: deepClone(this.innerValue), |
228 | 239 | historyOnly: this.historyOnly, |
229 | 240 | aggregation: this.aggregation, |
241 | + timezone: this.timezone, | |
230 | 242 | isEdit: this.isEdit |
231 | 243 | } |
232 | 244 | ); | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | import { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; |
18 | 18 | import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | -import { Observable } from 'rxjs'; | |
19 | +import { Observable, of } from 'rxjs'; | |
20 | 20 | import { map, mergeMap, share, tap } from 'rxjs/operators'; |
21 | 21 | import { Store } from '@ngrx/store'; |
22 | 22 | import { AppState } from '@app/core/core.state'; |
... | ... | @@ -43,10 +43,6 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af |
43 | 43 | |
44 | 44 | defaultTimezoneId: string = null; |
45 | 45 | |
46 | - timezones$ = getTimezones().pipe( | |
47 | - share() | |
48 | - ); | |
49 | - | |
50 | 46 | @Input() |
51 | 47 | set defaultTimezone(timezone: string) { |
52 | 48 | if (this.defaultTimezoneId !== timezone) { |
... | ... | @@ -138,23 +134,20 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af |
138 | 134 | |
139 | 135 | writeValue(value: string | null): void { |
140 | 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 | 151 | this.dirty = true; |
159 | 152 | } |
160 | 153 | |
... | ... | @@ -170,14 +163,12 @@ export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, Af |
170 | 163 | this.ignoreClosePanel = false; |
171 | 164 | } else { |
172 | 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 | 187 | fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { |
197 | 188 | this.searchText = searchText; |
198 | 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 | 196 | clear() { | ... | ... |
... | ... | @@ -430,6 +430,38 @@ export class EntityDataUpdate extends DataUpdate<EntityData> { |
430 | 430 | constructor(msg: EntityDataUpdateMsg) { |
431 | 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 | 467 | export class AlarmDataUpdate extends DataUpdate<AlarmData> { |
... | ... | @@ -468,6 +500,8 @@ export class TelemetrySubscriber { |
468 | 500 | |
469 | 501 | private zone: NgZone; |
470 | 502 | |
503 | + private tsOffset = 0; | |
504 | + | |
471 | 505 | public subscriptionCommands: Array<WebsocketCmd>; |
472 | 506 | |
473 | 507 | public data$ = this.dataSubject.asObservable(); |
... | ... | @@ -522,6 +556,10 @@ export class TelemetrySubscriber { |
522 | 556 | this.reconnectSubject.complete(); |
523 | 557 | } |
524 | 558 | |
559 | + public setTsOffset(tsOffset: number) { | |
560 | + this.tsOffset = tsOffset; | |
561 | + } | |
562 | + | |
525 | 563 | public onData(message: SubscriptionUpdate) { |
526 | 564 | const cmdId = message.subscriptionId; |
527 | 565 | let keys: string[]; |
... | ... | @@ -545,6 +583,9 @@ export class TelemetrySubscriber { |
545 | 583 | } |
546 | 584 | |
547 | 585 | public onEntityData(message: EntityDataUpdate) { |
586 | + if (this.tsOffset) { | |
587 | + message.prepareData(this.tsOffset); | |
588 | + } | |
548 | 589 | if (this.zone) { |
549 | 590 | this.zone.run( |
550 | 591 | () => { | ... | ... |
... | ... | @@ -15,11 +15,9 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 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 | 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 | 22 | const moment = moment_; |
25 | 23 | |
... | ... | @@ -97,10 +95,12 @@ export interface Timewindow { |
97 | 95 | hideInterval?: boolean; |
98 | 96 | hideAggregation?: boolean; |
99 | 97 | hideAggInterval?: boolean; |
98 | + hideTimezone?: boolean; | |
100 | 99 | selectedTab?: TimewindowType; |
101 | 100 | realtime?: RealtimeWindow; |
102 | 101 | history?: HistoryWindow; |
103 | 102 | aggregation?: Aggregation; |
103 | + timezone?: string; | |
104 | 104 | } |
105 | 105 | |
106 | 106 | export interface SubscriptionAggregation extends Aggregation { |
... | ... | @@ -112,6 +112,8 @@ export interface SubscriptionAggregation extends Aggregation { |
112 | 112 | export interface SubscriptionTimewindow { |
113 | 113 | startTs?: number; |
114 | 114 | quickInterval?: QuickTimeInterval; |
115 | + timezone?: string; | |
116 | + tsOffset?: number; | |
115 | 117 | realtimeWindowMs?: number; |
116 | 118 | fixedWindow?: FixedWindow; |
117 | 119 | aggregation?: SubscriptionAggregation; |
... | ... | @@ -121,6 +123,7 @@ export interface WidgetTimewindow { |
121 | 123 | minTime?: number; |
122 | 124 | maxTime?: number; |
123 | 125 | interval?: number; |
126 | + timezone?: string; | |
124 | 127 | stDiff?: number; |
125 | 128 | } |
126 | 129 | |
... | ... | @@ -178,6 +181,7 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { |
178 | 181 | hideInterval: false, |
179 | 182 | hideAggregation: false, |
180 | 183 | hideAggInterval: false, |
184 | + hideTimezone: false, | |
181 | 185 | selectedTab: TimewindowType.REALTIME, |
182 | 186 | realtime: { |
183 | 187 | realtimeType: RealtimeWindowType.LAST_INTERVAL, |
... | ... | @@ -209,6 +213,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T |
209 | 213 | model.hideInterval = value.hideInterval; |
210 | 214 | model.hideAggregation = value.hideAggregation; |
211 | 215 | model.hideAggInterval = value.hideAggInterval; |
216 | + model.hideTimezone = value.hideTimezone; | |
212 | 217 | if (isUndefined(value.selectedTab)) { |
213 | 218 | if (value.realtime) { |
214 | 219 | model.selectedTab = TimewindowType.REALTIME; |
... | ... | @@ -266,6 +271,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T |
266 | 271 | } |
267 | 272 | model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); |
268 | 273 | } |
274 | + model.timezone = value.timezone; | |
269 | 275 | } |
270 | 276 | return model; |
271 | 277 | } |
... | ... | @@ -292,6 +298,7 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, |
292 | 298 | hideInterval: timewindow.hideInterval || false, |
293 | 299 | hideAggregation: timewindow.hideAggregation || false, |
294 | 300 | hideAggInterval: timewindow.hideAggInterval || false, |
301 | + hideTimezone: timewindow.hideTimezone || false, | |
295 | 302 | selectedTab: TimewindowType.HISTORY, |
296 | 303 | history: { |
297 | 304 | historyType: HistoryWindowType.FIXED, |
... | ... | @@ -304,7 +311,8 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, |
304 | 311 | aggregation: { |
305 | 312 | type: aggType, |
306 | 313 | limit |
307 | - } | |
314 | + }, | |
315 | + timezone: timewindow.timezone | |
308 | 316 | }; |
309 | 317 | return historyTimewindow; |
310 | 318 | } |
... | ... | @@ -318,8 +326,15 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num |
318 | 326 | interval: SECOND, |
319 | 327 | limit: timeService.getMaxDatapointsLimit(), |
320 | 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 | 338 | let aggTimewindow = 0; |
324 | 339 | if (stateData) { |
325 | 340 | subscriptionTimewindow.aggregation.type = AggregationType.NONE; |
... | ... | @@ -345,20 +360,25 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num |
345 | 360 | } |
346 | 361 | } |
347 | 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 | 366 | subscriptionTimewindow.quickInterval = timewindow.realtime.quickInterval; |
367 | + subscriptionTimewindow.startTs = calculateIntervalStartTime(timewindow.realtime.quickInterval, currentDate); | |
350 | 368 | } else { |
351 | 369 | subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; |
370 | + subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; | |
352 | 371 | } |
353 | 372 | subscriptionTimewindow.aggregation.interval = |
354 | 373 | timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, |
355 | 374 | subscriptionTimewindow.aggregation.type); |
356 | - subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; | |
357 | - const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; | |
358 | 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 | 383 | } else { |
364 | 384 | let historyType = timewindow.history.historyType; |
... | ... | @@ -372,23 +392,24 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num |
372 | 392 | } |
373 | 393 | } |
374 | 394 | if (historyType === HistoryWindowType.LAST_INTERVAL) { |
375 | - const currentTime = Date.now(); | |
395 | + const currentDate = getCurrentTime(timewindow.timezone); | |
396 | + const currentTime = currentDate.valueOf(); | |
376 | 397 | subscriptionTimewindow.fixedWindow = { |
377 | 398 | startTimeMs: currentTime - timewindow.history.timewindowMs, |
378 | 399 | endTimeMs: currentTime |
379 | 400 | }; |
380 | 401 | aggTimewindow = timewindow.history.timewindowMs; |
381 | 402 | } else if (historyType === HistoryWindowType.INTERVAL) { |
382 | - const currentDate = moment(); | |
403 | + const currentDate = getCurrentTime(timewindow.timezone); | |
383 | 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 | 408 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
388 | 409 | } else { |
389 | 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 | 414 | aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; |
394 | 415 | } |
... | ... | @@ -404,88 +425,85 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num |
404 | 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 | 429 | switch (interval) { |
410 | 430 | case QuickTimeInterval.CURRENT_HOUR: |
411 | - return currentDate.diff(currentDate.clone().startOf('hour')) | |
431 | + return HOUR; | |
412 | 432 | case QuickTimeInterval.CURRENT_DAY: |
413 | 433 | case QuickTimeInterval.CURRENT_DAY_SO_FAR: |
414 | - return currentDate.diff(currentDate.clone().startOf('day')); | |
434 | + return DAY; | |
415 | 435 | case QuickTimeInterval.CURRENT_WEEK: |
416 | 436 | case QuickTimeInterval.CURRENT_WEEK_SO_FAR: |
417 | - return currentDate.diff(currentDate.clone().startOf('week')); | |
437 | + return WEEK; | |
418 | 438 | case QuickTimeInterval.CURRENT_MONTH: |
419 | 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 | 441 | case QuickTimeInterval.CURRENT_YEAR: |
422 | 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 | 488 | switch (interval) { |
471 | 489 | case QuickTimeInterval.YESTERDAY: |
472 | 490 | currentDate.subtract(1, 'days'); |
473 | - return currentDate.startOf('day').valueOf(); | |
491 | + return currentDate.startOf('day').valueOf(); | |
474 | 492 | case QuickTimeInterval.DAY_BEFORE_YESTERDAY: |
475 | 493 | currentDate.subtract(2, 'days'); |
476 | 494 | return currentDate.startOf('day').valueOf(); |
477 | 495 | case QuickTimeInterval.THIS_DAY_LAST_WEEK: |
478 | 496 | currentDate.subtract(1, 'weeks'); |
479 | - return currentDate.startOf('day').valueOf(); | |
497 | + return currentDate.startOf('day').valueOf(); | |
480 | 498 | case QuickTimeInterval.PREVIOUS_WEEK: |
481 | 499 | currentDate.subtract(1, 'weeks'); |
482 | - return currentDate.startOf('week').valueOf(); | |
500 | + return currentDate.startOf('week').valueOf(); | |
483 | 501 | case QuickTimeInterval.PREVIOUS_MONTH: |
484 | 502 | currentDate.subtract(1, 'months'); |
485 | - return currentDate.startOf('month').valueOf(); | |
503 | + return currentDate.startOf('month').valueOf(); | |
486 | 504 | case QuickTimeInterval.PREVIOUS_YEAR: |
487 | 505 | currentDate.subtract(1, 'years'); |
488 | - return currentDate.startOf('year').valueOf(); | |
506 | + return currentDate.startOf('year').valueOf(); | |
489 | 507 | case QuickTimeInterval.CURRENT_HOUR: |
490 | 508 | return currentDate.startOf('hour').valueOf(); |
491 | 509 | case QuickTimeInterval.CURRENT_DAY: |
... | ... | @@ -500,8 +518,6 @@ export function calculateIntervalStartTime(interval: QuickTimeInterval, startTS |
500 | 518 | case QuickTimeInterval.CURRENT_YEAR: |
501 | 519 | case QuickTimeInterval.CURRENT_YEAR_SO_FAR: |
502 | 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 | 576 | cloned.hideInterval = timewindow.hideInterval || false; |
561 | 577 | cloned.hideAggregation = timewindow.hideAggregation || false; |
562 | 578 | cloned.hideAggInterval = timewindow.hideAggInterval || false; |
579 | + cloned.hideTimezone = timewindow.hideTimezone || false; | |
563 | 580 | if (isDefined(timewindow.selectedTab)) { |
564 | 581 | cloned.selectedTab = timewindow.selectedTab; |
565 | 582 | if (timewindow.selectedTab === TimewindowType.REALTIME) { |
... | ... | @@ -569,6 +586,7 @@ export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { |
569 | 586 | } |
570 | 587 | } |
571 | 588 | cloned.aggregation = deepClone(timewindow.aggregation); |
589 | + cloned.timezone = timewindow.timezone; | |
572 | 590 | return cloned; |
573 | 591 | } |
574 | 592 | |
... | ... | @@ -718,60 +736,50 @@ export interface TimezoneInfo { |
718 | 736 | let timezones: TimezoneInfo[] = null; |
719 | 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 | 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 | +} | ... | ... |