Skip to content

Commit

Permalink
NAS-130254 / 25.04 / Setting UK/GB date results in error in Reporting…
Browse files Browse the repository at this point in the history
… graphs (#10465)

* NAS-130254: Setting UK/GB date results in error in Reporting graphs

* NAS-130254: PR Update
  • Loading branch information
AlexKarpov98 authored Aug 19, 2024
1 parent 6913c9f commit fc32b34
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('WidgetSysInfoActiveComponent', () => {
}),
mockProvider(LocaleService, {
getDateAndTime: () => ['2024-03-15', '10:34:11'],
getDateFromString: (date: string) => new Date(date),
}),
provideMockStore({
selectors: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ export class WidgetSysInfoActiveComponent {
version = computed(() => getSystemVersion(this.systemInfo().version, this.systemInfo().codename));
uptime = computed(() => this.systemInfo().uptime_seconds + this.realElapsedSeconds());
datetime = computed(() => {
const [dateValue, timeValue] = this.localeService.getDateAndTime(this.systemInfo().timezone);
return new Date(`${dateValue} ${timeValue}`).getTime() + (this.realElapsedSeconds() * 1000);
const [dateValue, timeValue] = this.localeService.getDateAndTime();
const extractedDate = this.localeService.getDateFromString(`${dateValue} ${timeValue}`, this.systemInfo().timezone);

return extractedDate.getTime() + (this.realElapsedSeconds() * 1000);
});
isLoaded = computed(() => this.systemInfo());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe('WidgetSysInfoPassiveComponent', () => {
mockProvider(Router),
mockProvider(LocaleService, {
getDateAndTime: () => ['2024-03-15', '10:34:11'],
getDateFromString: (date: string) => new Date(date),
}),
mockProvider(WidgetResourcesService, {
systemInfo$: of({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ export class WidgetSysInfoPassiveComponent {
version = computed(() => getSystemVersion(this.systemInfo().version, this.systemInfo().codename));
uptime = computed(() => this.systemInfo().uptime_seconds + this.realElapsedSeconds());
datetime = computed(() => {
const [dateValue, timeValue] = this.localeService.getDateAndTime(this.systemInfo().timezone);
return new Date(`${dateValue} ${timeValue}`).getTime() + (this.realElapsedSeconds() * 1000);
const [dateValue, timeValue] = this.localeService.getDateAndTime();
const extractedDate = this.localeService.getDateFromString(`${dateValue} ${timeValue}`, this.systemInfo().timezone);

return extractedDate.getTime() + (this.realElapsedSeconds() * 1000);
});
isLoaded = computed(() => this.systemInfo());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('WidgetSystemUptimeComponent', () => {
providers: [
mockProvider(LocaleService, {
getDateAndTime: () => ['2024-03-15', '10:34:11'],
getDateFromString: (date: string) => new Date(date),
}),
mockProvider(WidgetResourcesService, {
systemInfo$: of({
Expand Down Expand Up @@ -87,6 +88,7 @@ describe('WidgetSystemUptimeComponent', () => {
providers: [
mockProvider(LocaleService, {
getDateAndTime: () => ['2024-03-15', '10:34:11'],
getDateFromString: (date: string) => new Date(date),
}),
mockProvider(WidgetResourcesService, {
systemInfo$: of({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ export class WidgetSystemUptimeComponent implements WidgetComponent {
});

datetime = computed(() => {
const [dateValue, timeValue] = this.localeService.getDateAndTime(this.loadedSystemInfo().timezone);
return new Date(`${dateValue} ${timeValue}`).getTime() + (this.realElapsedSeconds() * 1000);
const [dateValue, timeValue] = this.localeService.getDateAndTime();
const extractedDate = this.localeService.getDateFromString(
`${dateValue} ${timeValue}`,
this.loadedSystemInfo().timezone,
);

return extractedDate.getTime() + (this.realElapsedSeconds() * 1000);
});

constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { UUID } from 'angular2-uuid';
import { add, isToday, sub } from 'date-fns';
import { zonedTimeToUtc } from 'date-fns-tz';
import {
add, isToday, sub,
} from 'date-fns';
import _ from 'lodash';
import {
BehaviorSubject, Subscription, timer,
Expand All @@ -36,6 +37,7 @@ import {
import { refreshInterval } from 'app/pages/reports-dashboard/reports.constants';
import { ReportsService } from 'app/pages/reports-dashboard/reports.service';
import { formatData } from 'app/pages/reports-dashboard/utils/report.utils';
import { LocaleService } from 'app/services/locale.service';
import { ThemeService } from 'app/services/theme/theme.service';
import { AppState } from 'app/store';
import { selectTheme, waitForPreferences } from 'app/store/preferences/preferences.selectors';
Expand Down Expand Up @@ -133,9 +135,10 @@ export class ReportComponent implements OnInit, OnChanges {
private store$: Store<AppState>,
private formatDateTimePipe: FormatDateTimePipe,
private themeService: ThemeService,
@Inject(DOCUMENT) private document: Document,
private reportsService: ReportsService,
private cdr: ChangeDetectorRef,
private localeService: LocaleService,
@Inject(DOCUMENT) private document: Document,
) {
this.reportsService.legendEventEmitterObs$.pipe(untilDestroyed(this)).subscribe({
next: (data: LegendDataWithStackedTotalHtml) => {
Expand Down Expand Up @@ -355,7 +358,7 @@ export class ReportComponent implements OnInit, OnChanges {
}

getDateFromString(timestamp: string): Date {
return zonedTimeToUtc(new Date(timestamp), this.timezone);
return this.localeService.getDateFromString(timestamp, this.timezone);
}

// Convert timespan to start/end options
Expand Down
116 changes: 116 additions & 0 deletions src/app/services/locale.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { LocaleService } from 'app/services/locale.service';
import { waitForPreferences } from 'app/store/preferences/preferences.selectors';
import { selectTimezone } from 'app/store/system-config/system-config.selectors';

describe('LocaleService', () => {
let spectator: SpectatorService<LocaleService>;
let service: LocaleService;

const createService = createServiceFactory({
service: LocaleService,
mocks: [Store],
});

beforeEach(() => {
spectator = createService();
service = spectator.service;

const store$ = spectator.inject(Store);
store$.select.mockImplementation((selector: unknown) => {
if (selector === selectTimezone) {
return of('UTC');
}

if (selector === waitForPreferences) {
return of({ dateFormat: 'yyyy-MM-dd', timeFormat: 'HH:mm:ss' });
}

return of(null);
});

jest.useFakeTimers().setSystemTime(new Date('2024-08-14T14:14:27Z'));
});

afterEach(() => {
jest.useRealTimers();
});

describe('getDateFormatOptions', () => {
it('should return correct date format options for default timezone', () => {
const options = service.getDateFormatOptions();
expect(options).toEqual([
{ label: '2024-08-14', value: 'yyyy-MM-dd' },
{ label: 'August 14, 2024', value: 'MMMM d, yyyy' },
{ label: '14 August, 2024', value: 'd MMMM, yyyy' },
{ label: 'Aug 14, 2024', value: 'MMM d, yyyy' },
{ label: '14 Aug 2024', value: 'd MMM yyyy' },
{ label: '08/14/2024', value: 'MM/dd/yyyy' },
{ label: '14/08/2024', value: 'dd/MM/yyyy' },
{ label: '14.08.2024', value: 'dd.MM.yyyy' },
]);
});
});

describe('getTimeFormatOptions', () => {
it('should return correct time format options for default timezone', () => {
const options = service.getTimeFormatOptions();
expect(options).toEqual([
{ label: '17:14:27 (24 Hours)', value: 'HH:mm:ss' },
{ label: '05:14:27 pm', value: "hh:mm:ss aaaaa'm'" },
{ label: '05:14:27 PM', value: 'hh:mm:ss aa' },
]);
});
});

describe('getDateFromString', () => {
it('should correctly parse a valid date string with default timezone', () => {
const date = service.getDateFromString('14/08/2024 02:00:00');
expect(date.toISOString()).toBe('2024-08-13T23:00:00.000Z');
});

it('should correctly parse a valid date string with another format with default timezone', () => {
const date = service.getDateFromString('14.08.2024 02:00:00');
expect(date.toISOString()).toBe('2024-08-13T23:00:00.000Z');
});

it('should throw an error for an invalid date string', () => {
expect(() => service.getDateFromString('invalid date')).toThrow('Invalid date format: invalid date');
});
});

describe('getPreferredDateFormat', () => {
it('should return the preferred date format', () => {
expect(service.getPreferredDateFormat()).toBe('yyyy-MM-dd');
});
});

describe('getPreferredTimeFormat', () => {
it('should return the preferred time format', () => {
expect(service.getPreferredTimeFormat()).toBe('HH:mm:ss');
});
});

describe('getDateAndTime', () => {
it('should return the correct date and time for default timezone', () => {
const [date, time] = service.getDateAndTime();
expect(date).toBe('2024-08-14');
expect(time).toBe('17:14:27');
});

it('should return the correct date and time for a specified timezone', () => {
const [date, time] = service.getDateAndTime('Europe/Kiev');
expect(date).toBe('2024-08-14');
expect(time).toBe('17:14:27');
});
});

describe('formatDateTimeToDateFns', () => {
it('should correctly format date-time string to date-fns format', () => {
const formatted = service.formatDateTimeToDateFns('YYYY-MM-DD A');
expect(formatted).toBe('yyyy-MM-dd aa');
});
});
});
27 changes: 26 additions & 1 deletion src/app/services/locale.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { marker as T } from '@biesbjerg/ngx-translate-extract-marker';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { format, utcToZonedTime } from 'date-fns-tz';
import { isValid, parse } from 'date-fns';
import { format, utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
import { combineLatest } from 'rxjs';
import { Option } from 'app/interfaces/option.interface';
import { AppState } from 'app/store';
Expand Down Expand Up @@ -68,6 +69,30 @@ export class LocaleService {
];
}

getDateFromString(timestamp: string, timezone?: string): Date {
const normalizedTimestamp = timestamp.trim();

const dateFormats = this.getDateFormatOptions().map((option) => option.value);
const timeFormats = this.getTimeFormatOptions().map((option) => option.value);

const formats = [
...dateFormats,
...dateFormats.flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
] as string[];

for (const dateFormat of formats) {
const parsedDate = parse(normalizedTimestamp, dateFormat, new Date());
if (isValid(parsedDate)) {
if (timezone) {
return zonedTimeToUtc(parsedDate, timezone);
}
return parsedDate;
}
}

throw new Error(`Invalid date format: ${timestamp}`);
}

getPreferredDateFormat(): string {
return this.dateFormat;
}
Expand Down

0 comments on commit fc32b34

Please sign in to comment.