From 6ad46c3054d5d4bcad74988c5a73b74d9e63ea93 Mon Sep 17 00:00:00 2001 From: Varun Jain Date: Fri, 31 Jul 2026 22:55:19 +0530 Subject: [PATCH] Fix historical market data dates shifting to the previous day Dates fetched via fetchMarketDataBySymbol() were kept as UTC instants, so date-fns format() calls downstream (which read local time) rendered the previous calendar day for users in timezones behind UTC. Normalize to the local date matching the UTC calendar date at the fetch boundary. Closes #6177 --- CHANGELOG.md | 4 +++ libs/common/src/lib/helper.spec.ts | 33 ++++++++++++++++++++++++ libs/common/src/lib/helper.ts | 8 ++++++ libs/ui/src/lib/services/data.service.ts | 7 +++-- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6b3ef37..84f2882be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the platform logo to the account selector in the create or update activity dialog +### Fixed + +- Fixed the date handling in the historical market data editor so entries no longer shift to the previous day for users in timezones behind UTC + ## 3.42.0 - 2026-08-04 ### Changed diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index 669c42e32..0a8637108 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -4,6 +4,7 @@ import { } from '@ghostfolio/common/config'; import { extractNumberFromString, + getLocalDateFromUtcDate, getNumberFormatGroup, getStringOrNull, getStringOrUndefined, @@ -355,4 +356,36 @@ describe('Helper', () => { ).toEqual(true); }); }); + + describe('Get local date from UTC date', () => { + const originalTz = process.env.TZ; + + afterEach(() => { + process.env.TZ = originalTz; + }); + + it('Preserve the calendar date in a timezone behind UTC', () => { + process.env.TZ = 'America/New_York'; + + const localDate = getLocalDateFromUtcDate( + new Date('2026-01-05T00:00:00.000Z') + ); + + expect(localDate.getFullYear()).toEqual(2026); + expect(localDate.getMonth()).toEqual(0); + expect(localDate.getDate()).toEqual(5); + }); + + it('Preserve the calendar date in a timezone ahead of UTC', () => { + process.env.TZ = 'Asia/Tokyo'; + + const localDate = getLocalDateFromUtcDate( + new Date('2026-01-05T00:00:00.000Z') + ); + + expect(localDate.getFullYear()).toEqual(2026); + expect(localDate.getMonth()).toEqual(0); + expect(localDate.getDate()).toEqual(5); + }); + }); }); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 6c0d6ea45..3819e5cee 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -359,6 +359,14 @@ export function getEmojiFlag(aCountryCode: string) { ); } +export function getLocalDateFromUtcDate(aUtcDate: Date) { + return new Date( + aUtcDate.getUTCFullYear(), + aUtcDate.getUTCMonth(), + aUtcDate.getUTCDate() + ); +} + export function getLocale() { return navigator.language ?? DEFAULT_LOCALE; } diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts index 7081f9b2c..942f1b2b6 100644 --- a/libs/ui/src/lib/services/data.service.ts +++ b/libs/ui/src/lib/services/data.service.ts @@ -16,7 +16,10 @@ import { UpdateTagDto, UpdateUserSettingDto } from '@ghostfolio/common/dtos'; -import { DATE_FORMAT } from '@ghostfolio/common/helper'; +import { + DATE_FORMAT, + getLocalDateFromUtcDate +} from '@ghostfolio/common/helper'; import { Access, AccessTokenResponse, @@ -586,7 +589,7 @@ export class DataService { .pipe( map((data) => { for (const item of data.marketData) { - item.date = parseISO(item.date); + item.date = getLocalDateFromUtcDate(parseISO(item.date)); } for (const item of data.splits ?? []) {