Browse Source

Fix start date of date ranges in local time zone

pull/8099/head
Thomas Kaul 5 days ago
parent
commit
78b998b2f5
  1. 9
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 41
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts
  3. 8
      apps/api/src/app/portfolio/portfolio.service.spec.ts
  4. 2
      apps/api/src/main.ts
  5. 22
      libs/common/src/lib/calculation-helper.spec.ts
  6. 20
      libs/common/src/lib/calculation-helper.ts

9
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -64,7 +64,6 @@ import {
format, format,
isAfter, isAfter,
isBefore, isBefore,
isEqual,
isFuture, isFuture,
isPast, isPast,
isWithinInterval, isWithinInterval,
@ -1418,8 +1417,8 @@ export abstract class PortfolioCalculator {
let netPerformanceAtStartDate: number; let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number;
const endDate = this.toChartDate(end); const endDate = resetHours(end);
const startDate = this.toChartDate(start); const startDate = resetHours(start);
for (const historicalDataItem of historicalData) { for (const historicalDataItem of historicalData) {
const date = resetHours(parseDate(historicalDataItem.date)); const date = resetHours(parseDate(historicalDataItem.date));
@ -1914,8 +1913,4 @@ export abstract class PortfolioCalculator {
await this.initialize(attempt + 1); await this.initialize(attempt + 1);
} }
} }
private toChartDate(aDate: Date) {
return isEqual(aDate, endOfDay(aDate)) ? resetHours(aDate) : aDate;
}
} }

41
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts

@ -226,5 +226,46 @@ describe('PortfolioCalculator', () => {
}) })
).toEqual(['2016-12-31', '2017-12-31']); ).toEqual(['2016-12-31', '2017-12-31']);
}); });
it('with a year to date date range', async () => {
const snapshot = await portfolioCalculator.getSnapshot();
snapshot.historicalData = [
getHistoricalDataItem({
date: '2016-12-30',
investment: 100,
netPerformance: 0
}),
getHistoricalDataItem({
date: '2016-12-31',
investment: 100,
netPerformance: 10
}),
getHistoricalDataItem({
date: '2017-01-03',
investment: 100,
netPerformance: 20
})
];
jest.useFakeTimers().setSystemTime(new Date('2017-01-04T02:30:00.000Z'));
const { endDate, startDate } = getIntervalFromDateRange({
dateRange: 'ytd'
});
const { chart } = await portfolioCalculator.getPerformance({
end: endDate,
start: startDate
});
jest.useRealTimers();
expect(
chart.map(({ date }) => {
return date;
})
).toEqual(['2016-12-31', '2017-01-03']);
});
}); });
}); });

8
apps/api/src/app/portfolio/portfolio.service.spec.ts

@ -16,7 +16,7 @@ import {
TAG_ID_EXCLUDE_FROM_ANALYSIS, TAG_ID_EXCLUDE_FROM_ANALYSIS,
UNKNOWN_KEY UNKNOWN_KEY
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { parseDate, resetHours } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { import {
Activity, Activity,
AssetProfileIdentifier, AssetProfileIdentifier,
@ -1045,7 +1045,7 @@ describe('PortfolioService', () => {
expect(getPerformanceOfCalculator).toHaveBeenCalledTimes(3); expect(getPerformanceOfCalculator).toHaveBeenCalledTimes(3);
expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(2, { expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(2, {
end: endOfDay(parseDate('2024-12-31')), end: endOfDay(parseDate('2024-12-31')),
start: resetHours(parseDate('2024-06-15')) start: parseDate('2024-06-15')
}); });
expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(3, { expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(3, {
end: endOfDay(parseDate('2025-06-15')), end: endOfDay(parseDate('2025-06-15')),
@ -1099,11 +1099,11 @@ describe('PortfolioService', () => {
expect(getPerformanceOfCalculator).toHaveBeenCalledTimes(3); expect(getPerformanceOfCalculator).toHaveBeenCalledTimes(3);
expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(1, { expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(1, {
end: endOfDay(parseDate('2026-01-01')), end: endOfDay(parseDate('2026-01-01')),
start: resetHours(parseDate('2025-01-01')) start: parseDate('2025-01-01')
}); });
expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(2, { expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(2, {
end: endOfDay(parseDate('2025-12-31')), end: endOfDay(parseDate('2025-12-31')),
start: resetHours(parseDate('2025-01-01')) start: parseDate('2025-01-01')
}); });
expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(3, { expect(getPerformanceOfCalculator).toHaveBeenNthCalledWith(3, {
end: endOfDay(parseDate('2026-01-01')), end: endOfDay(parseDate('2026-01-01')),

2
apps/api/src/main.ts

@ -31,8 +31,6 @@ import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
import { environment } from './environments/environment'; import { environment } from './environments/environment';
process.env.TZ = 'UTC';
const logger = new Logger('Bootstrap'); const logger = new Logger('Bootstrap');
const processWarningLogger = new Logger('ProcessWarning'); const processWarningLogger = new Logger('ProcessWarning');

22
libs/common/src/lib/calculation-helper.spec.ts

@ -6,6 +6,7 @@ import {
getIntervalFromDateRange getIntervalFromDateRange
} from './calculation-helper'; } from './calculation-helper';
import { DATE_FORMAT } from './helper'; import { DATE_FORMAT } from './helper';
import { DateRange } from './types';
describe('CalculationHelper', () => { describe('CalculationHelper', () => {
describe('interval from date range', () => { describe('interval from date range', () => {
@ -23,6 +24,27 @@ describe('CalculationHelper', () => {
// part of the interval // part of the interval
expect(startDate.getTime()).toEqual(new Date(2024, 0, 1).getTime() - 1); expect(startDate.getTime()).toEqual(new Date(2024, 0, 1).getTime() - 1);
}); });
it('Get interval of a date range relative to today', () => {
jest.useFakeTimers().setSystemTime(new Date(2024, 9, 10, 22, 0));
expect(
(['1d', 'mtd', 'wtd', 'ytd', '1y', '5y'] as DateRange[]).map(
(dateRange) => {
return getIntervalFromDateRange({ dateRange }).startDate;
}
)
).toEqual([
new Date(2024, 9, 9),
new Date(2024, 8, 30),
new Date(2024, 9, 6),
new Date(2023, 11, 31),
new Date(2023, 9, 10),
new Date(2019, 9, 10)
]);
jest.useRealTimers();
});
}); });
describe('annualized performance percentage', () => { describe('annualized performance percentage', () => {

20
libs/common/src/lib/calculation-helper.ts

@ -3,6 +3,7 @@ import {
endOfDay, endOfDay,
endOfYear, endOfYear,
max, max,
startOfDay,
startOfMonth, startOfMonth,
startOfWeek, startOfWeek,
startOfYear, startOfYear,
@ -12,7 +13,6 @@ import {
} from 'date-fns'; } from 'date-fns';
import { isFinite, isNumber } from 'lodash-es'; import { isFinite, isNumber } from 'lodash-es';
import { resetHours } from './helper';
import { DateRange } from './types'; import { DateRange } from './types';
export function getAnnualizedPerformancePercent({ export function getAnnualizedPerformancePercent({
@ -48,31 +48,25 @@ export function getIntervalFromDateRange(params: {
switch (dateRange) { switch (dateRange) {
case '1d': case '1d':
startDate = max([startDate, subDays(resetHours(new Date()), 1)]); startDate = max([startDate, subDays(startOfDay(new Date()), 1)]);
break; break;
case 'mtd': case 'mtd':
startDate = max([ startDate = max([startDate, subDays(startOfMonth(new Date()), 1)]);
startDate,
subDays(startOfMonth(resetHours(new Date())), 1)
]);
break; break;
case 'wtd': case 'wtd':
startDate = max([ startDate = max([
startDate, startDate,
subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1) subDays(startOfWeek(new Date(), { weekStartsOn: 1 }), 1)
]); ]);
break; break;
case 'ytd': case 'ytd':
startDate = max([ startDate = max([startDate, subDays(startOfYear(new Date()), 1)]);
startDate,
subDays(startOfYear(resetHours(new Date())), 1)
]);
break; break;
case '1y': case '1y':
startDate = max([startDate, subYears(resetHours(new Date()), 1)]); startDate = max([startDate, subYears(startOfDay(new Date()), 1)]);
break; break;
case '5y': case '5y':
startDate = max([startDate, subYears(resetHours(new Date()), 5)]); startDate = max([startDate, subYears(startOfDay(new Date()), 5)]);
break; break;
case 'max': case 'max':
break; break;

Loading…
Cancel
Save