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 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
... ...
... ... @@ -91,6 +91,7 @@
91 91 direction="left"
92 92 tooltipPosition="below"
93 93 aggregation="true"
  94 + timezone="true"
94 95 [(ngModel)]="dashboardCtx.dashboardTimewindow">
95 96 </tb-timewindow>
96 97 <tb-filters-edit [fxShow]="!isEdit && displayFilters()"
... ...
... ... @@ -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 +}
... ...