fix(grid): keep tz-aware Now values and duckdb lockfile in sync

This commit is contained in:
t8y2
2026-09-13 19:54:53 +08:00
parent ba77d94815
commit 91da8b2a9a
4 changed files with 37 additions and 6 deletions
-1
View File
@@ -1252,7 +1252,6 @@ dependencies = [
"fs2",
"futures",
"httpdate",
"iana-time-zone",
"insta",
"json5",
"jsonwebtoken",
@@ -4,7 +4,7 @@ import type { FocusOutsideEvent, PointerDownOutsideEvent } from "reka-ui";
import { CalendarClock, ChevronDown, ChevronUp, CircleSlash } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { formatTemporalInputValue, parseTemporalInputValue, stepTemporalInputValue, temporalOffsetSuffix, type TemporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
import { formatTemporalInputValue, hostTimezoneOffsetSuffix, parseTemporalInputValue, stepTemporalInputValue, temporalOffsetSuffix, type TemporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
const props = withDefaults(
defineProps<{
@@ -192,9 +192,12 @@ function setNow() {
const now = new Date();
const dateText = [String(now.getFullYear()).padStart(4, "0"), String(now.getMonth() + 1).padStart(2, "0"), String(now.getDate()).padStart(2, "0")].join("-");
const nextTime = [String(now.getHours()).padStart(2, "0"), String(now.getMinutes()).padStart(2, "0"), String(now.getSeconds()).padStart(2, "0")].join(":") + nowFractionSuffix(now);
// tz-aware columns must carry an explicit offset, otherwise the server parses
// the host wall clock in its own session timezone
const offsetSuffix = temporalOffsetSuffix(localValue.value, props.kind) ? hostTimezoneOffsetSuffix(now) : "";
if (props.kind === "date") setModelValue(dateText, true);
else if (props.kind === "time") setModelValue(nextTime, true);
else setModelValue(`${dateText} ${nextTime}`, true);
else if (props.kind === "time") setModelValue(`${nextTime}${offsetSuffix}`, true);
else setModelValue(`${dateText} ${nextTime}${offsetSuffix}`, true);
}
function finishCommit() {
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { formatTemporalInputValue, parseTemporalInputValue, stepTemporalInputValue, temporalCellEditorConfig, temporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
import { afterEach, describe, expect, it, vi } from "vitest";
import { formatTemporalInputValue, hostTimezoneOffsetSuffix, parseTemporalInputValue, stepTemporalInputValue, temporalCellEditorConfig, temporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
describe("dataGridTemporalEditor", () => {
it("resolves temporal editor configs with fractional precision", () => {
@@ -54,4 +54,26 @@ describe("dataGridTemporalEditor", () => {
expect(formatTemporalInputValue("2026-07-09 12:34:56", "datetime")).toBe("2026-07-09T12:34:56");
expect(parseTemporalInputValue("2026-07-09T12:34:56", "datetime")).toBe("2026-07-09 12:34:56");
});
it("formats the host timezone offset for tz-aware Now values", () => {
const now = new Date("2026-09-13T12:00:00Z");
const spy = vi.spyOn(Date.prototype, "getTimezoneOffset");
try {
spy.mockReturnValue(-480);
expect(hostTimezoneOffsetSuffix(now)).toBe("+08:00");
spy.mockReturnValue(330);
expect(hostTimezoneOffsetSuffix(now)).toBe("-05:30");
spy.mockReturnValue(0);
expect(hostTimezoneOffsetSuffix(now)).toBe("+00:00");
} finally {
spy.mockRestore();
}
});
afterEach(() => {
vi.restoreAllMocks();
});
});
@@ -78,6 +78,13 @@ export function temporalOffsetSuffix(value: string, kind: TemporalCellEditorKind
return basePattern.test(withoutOffset) ? offset : "";
}
export function hostTimezoneOffsetSuffix(now: Date): string {
const minutes = -now.getTimezoneOffset();
const sign = minutes >= 0 ? "+" : "-";
const abs = Math.abs(minutes);
return `${sign}${String(Math.floor(abs / 60)).padStart(2, "0")}:${String(abs % 60).padStart(2, "0")}`;
}
export function stepTemporalInputValue(value: string, kind: TemporalCellEditorKind, part: TemporalCellEditorPart, delta: number): string {
const dateParts = temporalDateParts(value);
const timeParts = temporalTimeParts(value, kind);