diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index e7c7ea5ba..f885b0cb6 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -13,6 +13,7 @@ import { import { DATE_FORMAT, calculateBenchmarkTrend, + eachDayOfInterval, parseDate, resetHours } from '@ghostfolio/common/helper'; @@ -28,13 +29,7 @@ import { BenchmarkTrend } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { SymbolProfile } from '@prisma/client'; import { Big } from 'big.js'; -import { - differenceInDays, - eachDayOfInterval, - format, - isSameDay, - subDays -} from 'date-fns'; +import { differenceInDays, format, isSameDay, subDays } from 'date-fns'; import { isNumber, last, uniqBy } from 'lodash'; import ms from 'ms'; @@ -227,15 +222,12 @@ export class BenchmarkService { const marketData: { date: string; value: number }[] = []; const days = differenceInDays(endDate, startDate) + 1; - const step = Math.round(days / Math.min(days, MAX_CHART_ITEMS)); - const dates = eachDayOfInterval( - { start: startDate, end: endDate }, - { step } - ); - - if (!isSameDay(last(dates), endDate)) { - dates.push(resetHours(endDate)); - } + const dates = eachDayOfInterval({ + start: startDate, + end: endDate, + step: Math.round(days / Math.min(days, MAX_CHART_ITEMS)), + includeEnd: true + }); const [currentSymbolItem, marketDataItems] = await Promise.all([ this.symbolService.get({ diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index a519b91cf..5059d312a 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -1,7 +1,12 @@ import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; -import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; +import { + DATE_FORMAT, + eachDayOfInterval, + parseDate, + resetHours +} from '@ghostfolio/common/helper'; import { DataProviderInfo, HistoricalDataItem, @@ -18,7 +23,6 @@ import { addDays, addMilliseconds, differenceInDays, - eachDayOfInterval, endOfDay, format, isBefore, @@ -203,11 +207,7 @@ export class PortfolioCalculator { const dataGatheringItems: IDataGatheringItem[] = []; const firstIndex = transactionPointsBeforeEndDate.length; - const dates = eachDayOfInterval({ start, end }, { step }); - - if (!isSameDay(last(dates), end)) { - dates.push(resetHours(end)); - } + const dates = eachDayOfInterval({ start, end, step, includeEnd: true }); if (transactionPointsBeforeEndDate.length > 0) { for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) { diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index 22a171168..bec289306 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -1,4 +1,7 @@ -import { extractNumberFromString } from '@ghostfolio/common/helper'; +import { + eachDayOfInterval, + extractNumberFromString +} from '@ghostfolio/common/helper'; describe('Helper', () => { describe('Extract number from string', () => { @@ -36,4 +39,75 @@ describe('Helper', () => { expect(extractNumberFromString({ value: 'X' })).toEqual(NaN); }); }); + describe('eachDayOfInterval', () => { + describe('start and end', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 2)), + new Date(Date.UTC(2021, 0, 3)) + ]; + it('as strings', () => { + const [start, end] = ['2021-01-01', '2021-01-03']; + expect(eachDayOfInterval({ start, end })).toEqual(expected); + }); + it('as numbers', () => { + const [start, end] = [Date.UTC(2021, 0, 1), Date.UTC(2021, 0, 3)]; + expect(eachDayOfInterval({ start, end })).toEqual(expected); + }); + it('as dates', () => { + const [start, end] = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 3)) + ]; + expect(eachDayOfInterval({ start, end })).toEqual(expected); + }); + }); + describe('step', () => { + const [start, end] = ['2021-01-01', '2021-01-06']; + it('step size of 2', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 3)), + new Date(Date.UTC(2021, 0, 5)) + ]; + expect(eachDayOfInterval({ start, end, step: 2 })).toEqual(expected); + }); + it('step size of 4', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 5)) + ]; + expect(eachDayOfInterval({ start, end, step: 4 })).toEqual(expected); + }); + it('step size of 5', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 6)) + ]; + expect(eachDayOfInterval({ start, end, step: 5 })).toEqual(expected); + }); + }); + describe('includeEnd', () => { + const [start, end] = ['2021-01-01', '2021-01-06']; + it('end is added', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 5)), + new Date(Date.UTC(2021, 0, 6)) + ]; + expect( + eachDayOfInterval({ start, end, step: 4, includeEnd: true }) + ).toEqual(expected); + }); + it('end is not duplicated', () => { + const expected = [ + new Date(Date.UTC(2021, 0, 1)), + new Date(Date.UTC(2021, 0, 6)) + ]; + expect( + eachDayOfInterval({ start, end, step: 5, includeEnd: true }) + ).toEqual(expected); + }); + }); + }); }); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index b49589aab..209cb43b6 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -375,6 +375,35 @@ export function resetHours(aDate: Date) { return new Date(Date.UTC(year, month, day)); } +export function eachDayOfInterval({ + start, + end, + step = 1, + includeEnd = false +}: { + start: Date | string | number; + end: Date | string | number; + step?: number; + includeEnd?: boolean; +}) { + [start, end] = [new Date(start), new Date(end)]; + [start, end] = [resetHours(start), resetHours(end)]; + [start, end] = [start.getTime(), end.getTime()]; + + const DAY_IN_MS = 1000 * 60 * 60 * 24; + const dates: number[] = []; + + for (let date = start; date <= end; date += step * DAY_IN_MS) { + dates.push(date); + } + + if (includeEnd && end !== dates?.[dates.length - 1]) { + dates.push(end); + } + + return dates.map((date) => new Date(date)); +} + export function resolveFearAndGreedIndex(aValue: number) { if (aValue <= 25) { return { emoji: '🥵', key: 'EXTREME_FEAR', text: 'Extreme Fear' };