diff --git a/core/util/historyUtils.ts b/core/util/historyUtils.ts index fc0939c7bcf..c52493281d7 100644 --- a/core/util/historyUtils.ts +++ b/core/util/historyUtils.ts @@ -10,13 +10,10 @@ import { getContinueGlobalPath } from "../util/paths.js"; // If useful elsewhere, helper funcs should move to core/util/index.ts or similar function getOffsetDatetime(date: Date): Date { - const offset = date.getTimezoneOffset(); - const offsetHours = Math.floor(offset / 60); - const offsetMinutes = offset % 60; - date.setHours(date.getHours() - offsetHours); - date.setMinutes(date.getMinutes() - offsetMinutes); - - return date; + // Shift by the whole offset at once: splitting it into hours and minutes + // rounds the wrong way for zones east of UTC that aren't a whole number of + // hours ahead, e.g. UTC+05:30 would be treated as UTC+06:30. + return new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000); } function asBasicISOString(date: Date): string { diff --git a/core/util/historyUtils.vitest.ts b/core/util/historyUtils.vitest.ts new file mode 100644 index 00000000000..caec41d849f --- /dev/null +++ b/core/util/historyUtils.vitest.ts @@ -0,0 +1,50 @@ +import fs from "fs"; +import os from "os"; +import path from "path"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { shareSession } from "./historyUtils"; + +describe("shareSession", () => { + const originalTz = process.env.TZ; + let outputDir: string; + + const shareAt = async (timeZone: string, isoTime: string) => { + process.env.TZ = timeZone; + vi.useFakeTimers({ toFake: ["Date"] }); + vi.setSystemTime(new Date(isoTime)); + + const ide = { writeFile: vi.fn(), openFile: vi.fn() }; + const fileUrl = await shareSession(ide as any, [], outputDir); + return path.basename(fileUrl); + }; + + beforeEach(() => { + outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "continue-share-")); + }); + + afterEach(() => { + vi.useRealTimers(); + process.env.TZ = originalTz; + fs.rmSync(outputDir, { recursive: true, force: true }); + }); + + it("names the file after local time in whole-hour timezones", async () => { + // UTC-04:00 in August + const name = await shareAt("America/New_York", "2026-08-17T10:00:00.000Z"); + expect(name).toBe("20260817T060000_session.md"); + }); + + it("names the file after local time in half-hour timezones", async () => { + // UTC+05:30 + const name = await shareAt("Asia/Kolkata", "2026-08-17T10:00:00.000Z"); + expect(name).toBe("20260817T153000_session.md"); + }); + + it("names the file after local time in quarter-hour timezones", async () => { + // UTC+05:45 + const name = await shareAt("Asia/Kathmandu", "2026-08-17T10:00:00.000Z"); + expect(name).toBe("20260817T154500_session.md"); + }); +});