From b009101b7b954e8462672a9da859267ebb71edc4 Mon Sep 17 00:00:00 2001 From: codex-bot Date: Sat, 24 Oct 2026 11:04:50 +0300 Subject: [PATCH] fix: show snapshot time in browser time zone (#101) --- .../AccountEventsPage.filters.test.tsx | 7 +++++++ src/pages/stock/ui/StockPage/StockPage.test.tsx | 4 +++- src/pages/stocks/ui/StocksPage/StocksPage.test.tsx | 4 +++- src/shared/i18n/locales/ru.ts | 6 +++--- src/shared/lib/display.test.ts | 13 ++++++++++--- src/shared/lib/display.ts | 2 +- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx index 7a135aa..ba652d0 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.filters.test.tsx @@ -72,6 +72,9 @@ function renderEvents(accountId: string, searches: string[]) { } describe("Upcoming event date filters", () => { + afterEach(() => { + vi.useRealTimers(); + }); beforeEach(() => { // Pin "today": the test picks today + 10 days, which must be inside the visible calendar grid. // Real dates can push it beyond the grid (e.g. 2026-10-23 -> 2 November). @@ -235,6 +238,10 @@ describe("Upcoming event date filters", () => { it.each(["account-1", "all"])( "applies dates only on submit, restores URL dates, and resets from empty results for %s", async (accountId) => { + // Pin "today" mid-month so "today + 10 days" stays in the calendar's + // visible month regardless of the real date. + vi.useFakeTimers({ toFake: ["Date"] }); + vi.setSystemTime(new Date("2026-10-15T12:00:00+03:00")); const [defaultFrom, defaultTo] = defaultPeriod(); const searches: string[] = []; server.use( diff --git a/src/pages/stock/ui/StockPage/StockPage.test.tsx b/src/pages/stock/ui/StockPage/StockPage.test.tsx index 720ad65..129c1db 100644 --- a/src/pages/stock/ui/StockPage/StockPage.test.tsx +++ b/src/pages/stock/ui/StockPage/StockPage.test.tsx @@ -413,7 +413,9 @@ describe("stock page", () => { expect(await screen.findByText("Продажа")).toBeVisible(); expect(screen.getByText("2 шт.")).toBeVisible(); expect(screen.getByText("Цена: 100 ₽")).toBeVisible(); - expect(screen.getByText(/02.01.2022, 10:00 UTC/)).toBeVisible(); + const snapshotTime = new Intl.DateTimeFormat("ru-RU", { dateStyle: "short", timeStyle: "short" }); + const operationTime = snapshotTime.format(new Date("2022-01-02T10:00:00Z")); + expect(screen.getByText(operationTime, { exact: false })).toBeVisible(); expect( requests.some( url => diff --git a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx index 76718f6..a9267ff 100644 --- a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx +++ b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx @@ -93,7 +93,9 @@ describe("stock search", () => { expect( await screen.findByRole("link", { name: /Сбербанк/ }), ).toHaveAttribute("href", "/stocks/e6123145-9665-43e0-8413-cd61b8aa9b13"); - expect(screen.getByText("Список получен 10.10.2026, 10:00")).toBeVisible(); + const snapshotTime = new Intl.DateTimeFormat("ru-RU", { dateStyle: "short", timeStyle: "short" }); + const fetchedAt = snapshotTime.format(new Date("2026-10-10T10:00:00Z")); + expect(screen.getByText(`Список получен ${fetchedAt}`)).toBeVisible(); expect(screen.getByText("Лот: 10 шт.")).toBeVisible(); expect(screen.getByText("ISIN: RU0009029540")).toBeVisible(); expect(screen.getAllByText("Мосбиржа")[0]).toBeVisible(); diff --git a/src/shared/i18n/locales/ru.ts b/src/shared/i18n/locales/ru.ts index 7bc3abb..caadfb6 100644 --- a/src/shared/i18n/locales/ru.ts +++ b/src/shared/i18n/locales/ru.ts @@ -420,7 +420,7 @@ const ru = { next: "Далее", noInstrument: "Без инструмента", noMoney: "Без движения денег", - occurredAt: "{{date}} UTC", + occurredAt: "{{date}}", open: "Открыть журнал", operationType: { buy: "Покупка", @@ -632,7 +632,7 @@ const ru = { position: "Моя позиция", previous: "Назад", price: "Цена", - priceAt: "Цена на {{date}} UTC", + priceAt: "Цена на {{date}}", priceTable: "Цены в таблице", priceTableHint: "Дневные закрытия · записей: {{count}} · сначала новые", priceUnavailable: "Цена недоступна", @@ -721,7 +721,7 @@ const ru = { SHARE_TYPE_GDR: "Депозитарная расписка", SHARE_TYPE_PREFERRED: "Привилегированная акция", }, - snapshotAt: "Позиция на {{date}} UTC", + snapshotAt: "Позиция на {{date}}", trading: { SECURITY_TRADING_STATUS_BREAK_IN_TRADING: "Перерыв в торгах", SECURITY_TRADING_STATUS_CLOSING_AUCTION: "Аукцион закрытия", diff --git a/src/shared/lib/display.test.ts b/src/shared/lib/display.test.ts index 826d772..578282f 100644 --- a/src/shared/lib/display.test.ts +++ b/src/shared/lib/display.test.ts @@ -12,9 +12,16 @@ describe("shared display helpers", () => { expect(formatDate("2027-09-10")).toBe("10.09.2027"); expect(formatDate("2024-02-29")).toBe("29.02.2024"); expect(formatDate("2027-09-10T00:30:00+03:00")).toBe("09.09.2027"); - expect(formatSnapshotTime("2027-09-10T00:30:00+03:00")).toBe( - "09.09.2027, 21:30", - ); + }); + it("formats snapshot time in the browser time zone", () => { + const instant = "2027-09-10T00:30:00+03:00"; + + const expected = new Intl.DateTimeFormat("ru-RU", { + dateStyle: "short", + timeStyle: "short", + }).format(new Date(instant)); + + expect(formatSnapshotTime(instant)).toBe(expected); }); it("formats decimal strings without financial arithmetic", () => { expect(formatMoney({ currency: "RUB", value: "-1234567.89" })).toBe( diff --git a/src/shared/lib/display.ts b/src/shared/lib/display.ts index 4a23e3f..db44e99 100644 --- a/src/shared/lib/display.ts +++ b/src/shared/lib/display.ts @@ -61,10 +61,10 @@ function formatMoney( return `${formatDecimal(decimal)} ${currency}`; } +// Без timeZone Intl берёт часовой пояс браузера пользователя. const snapshotFormatter = new Intl.DateTimeFormat("ru-RU", { dateStyle: "short", timeStyle: "short", - timeZone: "UTC", }); function formatSnapshotTime(value: string): string {