Browse Source

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
pull/7491/head
Varun Jain 4 weeks ago
parent
commit
6ad46c3054
  1. 4
      CHANGELOG.md
  2. 33
      libs/common/src/lib/helper.spec.ts
  3. 8
      libs/common/src/lib/helper.ts
  4. 7
      libs/ui/src/lib/services/data.service.ts

4
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 - 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 ## 3.42.0 - 2026-08-04
### Changed ### Changed

33
libs/common/src/lib/helper.spec.ts

@ -4,6 +4,7 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { import {
extractNumberFromString, extractNumberFromString,
getLocalDateFromUtcDate,
getNumberFormatGroup, getNumberFormatGroup,
getStringOrNull, getStringOrNull,
getStringOrUndefined, getStringOrUndefined,
@ -355,4 +356,36 @@ describe('Helper', () => {
).toEqual(true); ).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);
});
});
}); });

8
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() { export function getLocale() {
return navigator.language ?? DEFAULT_LOCALE; return navigator.language ?? DEFAULT_LOCALE;
} }

7
libs/ui/src/lib/services/data.service.ts

@ -16,7 +16,10 @@ import {
UpdateTagDto, UpdateTagDto,
UpdateUserSettingDto UpdateUserSettingDto
} from '@ghostfolio/common/dtos'; } from '@ghostfolio/common/dtos';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import {
DATE_FORMAT,
getLocalDateFromUtcDate
} from '@ghostfolio/common/helper';
import { import {
Access, Access,
AccessTokenResponse, AccessTokenResponse,
@ -586,7 +589,7 @@ export class DataService {
.pipe( .pipe(
map((data) => { map((data) => {
for (const item of data.marketData) { for (const item of data.marketData) {
item.date = parseISO(item.date); item.date = getLocalDateFromUtcDate(parseISO(item.date));
} }
for (const item of data.splits ?? []) { for (const item of data.splits ?? []) {

Loading…
Cancel
Save