Browse Source

Bugfix/normalize date in market data update endpoint

The bulk market data update endpoint parses the incoming date with
parseISO, which interprets a date-only string as LOCAL midnight. The data
gathering path stores market data at UTC midnight via resetHours. On a
server running in any time zone other than UTC the two therefore disagree,
and because MarketData is keyed on (dataSource, date, symbol) the upsert
misses the existing row and a duplicate is created one offset away.

Demonstration for the input "2026-08-07":

  UTC                parseISO -> 2026-08-07T00:00:00.000Z   matches
  Europe/Lisbon      parseISO -> 2026-08-06T23:00:00.000Z   off by -1h
  America/Sao_Paulo  parseISO -> 2026-08-07T03:00:00.000Z   off by +3h
  Asia/Tokyo         parseISO -> 2026-08-06T15:00:00.000Z   off by -9h

Using resetHours(parseDate(date)) yields 2026-08-07T00:00:00.000Z in every
zone and matches what market-data.service.ts already writes, so the endpoint
now agrees with the rest of the codebase rather than only on UTC hosts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull/7567/head
Miguel Rocha 1 week ago
parent
commit
df6a4c3aab
  1. 1
      CHANGELOG.md
  2. 10
      apps/api/src/app/endpoints/market-data/market-data.controller.ts

1
CHANGELOG.md

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the check for duplicates in the preview step of the activities import for activities without a comment
- Fixed the holdings mock data in the _Storybook_ story of the portfolio filter form component
- Fixed the date normalization in the market data update endpoint to prevent duplicate entries on servers running in a non-UTC time zone
## 3.44.0 - 2026-08-07

10
apps/api/src/app/endpoints/market-data/market-data.controller.ts

@ -4,7 +4,12 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos';
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import {
getCurrencyFromSymbol,
isCurrency,
parseDate,
resetHours
} from '@ghostfolio/common/helper';
import { MarketDataOfMarketsResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
@ -24,7 +29,6 @@ import {
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { DataSource, Prisma } from '@prisma/client';
import { parseISO } from 'date-fns';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
@Controller('market-data')
@ -99,7 +103,7 @@ export class MarketDataController {
dataSource,
marketPrice,
symbol,
date: parseISO(date),
date: resetHours(parseDate(date)),
state: 'CLOSE'
})
);

Loading…
Cancel
Save