| author | |
| committer | |
| log | 684d703b0a8a0632a18c8453277f7e5e6312b200 |
| tree | a5321f402ba9e7504c4cda558f83fa61b19d99ac |
| parent | 40ab7bf32a81a66b20571ed280dc238c2276dc61 |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 36 insertions(+), 8 deletions(-)
packages/shared/src/usageFormat.test.ts+17-1| ... | ... | @@ -1,5 +1,5 @@ |
| 1 | 1 | // @effect-diagnostics globalDate:off -- A fixed instant keeps calendar-window assertions deterministic. |
| 2 | import { describe, expect, it } from "vite-plus/test"; | |
| 2 | import { describe, expect, it, vi } from "vite-plus/test"; | |
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | enumerateHourStarts, |
| ... | ... | @@ -54,4 +54,20 @@ describe("hourly usage formatting", () => { |
| 54 | 54 | expect(window.sinceTime).toBe("2026-08-10T12:37:00.000Z"); |
| 55 | 55 | expect(window.untilTime).toBe("2026-08-11T12:37:00.000Z"); |
| 56 | 56 | }); |
| 57 | ||
| 58 | it("degrades an unknown resolved zone to UTC instead of crashing", () => { | |
| 59 | const resolved = new Intl.DateTimeFormat().resolvedOptions(); | |
| 60 | const resolvedOptions = vi | |
| 61 | .spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions") | |
| 62 | .mockReturnValue({ ...resolved, timeZone: "Etc/Unknown" }); | |
| 63 | ||
| 64 | try { | |
| 65 | const now = new Date("2026-08-11T12:37:42.123Z"); | |
| 66 | ||
| 67 | expect(makeWindow(1, now, "hour").timeZone).toBe("UTC"); | |
| 68 | expect(makeWindow(30, now).timeZone).toBe("UTC"); | |
| 69 | } finally { | |
| 70 | resolvedOptions.mockRestore(); | |
| 71 | } | |
| 72 | }); | |
| 57 | 73 | }); |
packages/shared/src/usageFormat.ts+19-7| ... | ... | @@ -179,13 +179,25 @@ export function makeWindow( |
| 179 | 179 | now = new Date(), |
| 180 | 180 | resolution: UsageResolution = "day", |
| 181 | 181 | ): UsageSummaryInput { |
| 182 | const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; | |
| 183 | const format = new Intl.DateTimeFormat("en-CA", { | |
| 184 | timeZone, | |
| 185 | year: "numeric", | |
| 186 | month: "2-digit", | |
| 187 | day: "2-digit", | |
| 188 | }); | |
| 182 | let timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; | |
| 183 | let format: Intl.DateTimeFormat; | |
| 184 | try { | |
| 185 | format = new Intl.DateTimeFormat("en-CA", { | |
| 186 | timeZone, | |
| 187 | year: "numeric", | |
| 188 | month: "2-digit", | |
| 189 | day: "2-digit", | |
| 190 | }); | |
| 191 | } catch { | |
| 192 | // An unknown zone should degrade to UTC rather than crash the page. | |
| 193 | timeZone = "UTC"; | |
| 194 | format = new Intl.DateTimeFormat("en-CA", { | |
| 195 | timeZone: "UTC", | |
| 196 | year: "numeric", | |
| 197 | month: "2-digit", | |
| 198 | day: "2-digit", | |
| 199 | }); | |
| 200 | } | |
| 189 | 201 | const untilDay = format.format(now); |
| 190 | 202 | if (resolution === "hour") { |
| 191 | 203 | // Minute-aligned bounds keep labels readable while still representing an |