fix(postgres): render temporal values in session timezone
This commit is contained in:
1 parent
a33768ef6e
commit
ba77d94815
7 files changed
+329
-40
No files matched your search
@@ -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, type TemporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
|
||||
import { formatTemporalInputValue, parseTemporalInputValue, stepTemporalInputValue, temporalOffsetSuffix, type TemporalCellEditorKind } from "@/lib/dataGrid/dataGridTemporalEditor";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -134,7 +134,7 @@ function updateTime(part: "hour" | "minute" | "second", rawValue: string | numbe
|
||||
const parts = { ...timeParts.value, [part]: normalizeTimePart(rawValue, part === "hour" ? 23 : 59) };
|
||||
const nextTime = `${parts.hour}:${parts.minute}:${parts.second}${fractionSuffix.value}`;
|
||||
if (props.kind === "time") {
|
||||
setModelValue(nextTime, true);
|
||||
setModelValue(`${nextTime}${temporalOffsetSuffix(localValue.value, props.kind)}`, true);
|
||||
return;
|
||||
}
|
||||
setDateTimeValue(dateParts.value.year, dateParts.value.month, dateParts.value.day, nextTime);
|
||||
@@ -162,7 +162,7 @@ function updateFractionValue(rawValue: string) {
|
||||
const digits = rawValue.replace(/\D/g, "").slice(0, maxLength);
|
||||
const nextTime = `${timeParts.value.hour}:${timeParts.value.minute}:${timeParts.value.second}${digits ? `.${digits}` : ""}`;
|
||||
if (props.kind === "time") {
|
||||
setModelValue(nextTime, true);
|
||||
setModelValue(`${nextTime}${temporalOffsetSuffix(localValue.value, props.kind)}`, true);
|
||||
return;
|
||||
}
|
||||
setDateTimeValue(dateParts.value.year, dateParts.value.month, dateParts.value.day, nextTime);
|
||||
@@ -267,7 +267,7 @@ function normalizeTimePart(value: string | number, max: number): string {
|
||||
function setDateTimeValue(year: number, month: number, day: number, time: string) {
|
||||
const dateText = [String(year).padStart(4, "0"), String(month).padStart(2, "0"), String(day).padStart(2, "0")].join("-");
|
||||
if (props.kind === "date") setModelValue(dateText, true);
|
||||
else setModelValue(`${dateText} ${time}`, true);
|
||||
else setModelValue(`${dateText} ${time}${temporalOffsetSuffix(localValue.value, props.kind)}`, true);
|
||||
}
|
||||
|
||||
function parseFractionDigits(value: string): string {
|
||||
|
||||
@@ -40,6 +40,15 @@ describe("dataGridTemporalEditor", () => {
|
||||
expect(stepTemporalInputValue(value, "datetime", "second", 1)).toBe("2026-07-09 12:34:57.123456");
|
||||
});
|
||||
|
||||
it("preserves timezone offsets when parsing or stepping temporal values", () => {
|
||||
const datetime = "2026-07-09 12:34:56.123456+12:00";
|
||||
const time = "12:34:56.123456-05:30";
|
||||
|
||||
expect(parseTemporalInputValue(time, "time")).toBe(time);
|
||||
expect(stepTemporalInputValue(datetime, "datetime", "hour", 1)).toBe("2026-07-09 13:34:56.123456+12:00");
|
||||
expect(stepTemporalInputValue(time, "time", "minute", 1)).toBe("12:35:56.123456-05:30");
|
||||
});
|
||||
|
||||
it("keeps ordinary datetime values at second precision", () => {
|
||||
expect(temporalCellEditorConfig("datetime")).toEqual({ kind: "datetime", fractionPrecision: 0 });
|
||||
expect(formatTemporalInputValue("2026-07-09 12:34:56", "datetime")).toBe("2026-07-09T12:34:56");
|
||||
|
||||
@@ -56,7 +56,10 @@ export function parseTemporalInputValue(value: string, kind: TemporalCellEditorK
|
||||
const text = value.trim();
|
||||
if (!text) return null;
|
||||
if (kind === "date") return text;
|
||||
if (kind === "time") return normalizeTimeInput(text) || text;
|
||||
if (kind === "time") {
|
||||
const normalized = normalizeTimeInput(text);
|
||||
return normalized ? `${normalized}${temporalOffsetSuffix(text, kind)}` : text;
|
||||
}
|
||||
|
||||
const datetime = text.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})(?::(\d{2})(\.\d{1,9})?)?$/);
|
||||
if (!datetime) return text;
|
||||
@@ -65,9 +68,20 @@ export function parseTemporalInputValue(value: string, kind: TemporalCellEditorK
|
||||
|
||||
export type TemporalCellEditorPart = "year" | "month" | "day" | "hour" | "minute" | "second";
|
||||
|
||||
export function temporalOffsetSuffix(value: string, kind: TemporalCellEditorKind): string {
|
||||
const text = value.trim();
|
||||
const offset = text.match(/(Z|[+-]\d{2}(?::?\d{2})?)$/i)?.[1];
|
||||
if (!offset) return "";
|
||||
|
||||
const withoutOffset = text.slice(0, -offset.length);
|
||||
const basePattern = kind === "time" ? /^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/ : /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
||||
return basePattern.test(withoutOffset) ? offset : "";
|
||||
}
|
||||
|
||||
export function stepTemporalInputValue(value: string, kind: TemporalCellEditorKind, part: TemporalCellEditorPart, delta: number): string {
|
||||
const dateParts = temporalDateParts(value);
|
||||
const timeParts = temporalTimeParts(value, kind);
|
||||
const offset = temporalOffsetSuffix(value, kind);
|
||||
|
||||
if (part === "year") dateParts.year = Math.max(1, Math.min(9999, dateParts.year + delta));
|
||||
else if (part === "month") dateParts.month = Math.max(1, Math.min(12, dateParts.month + delta));
|
||||
@@ -82,8 +96,8 @@ export function stepTemporalInputValue(value: string, kind: TemporalCellEditorKi
|
||||
const timeText = [timeParts.hour, timeParts.minute, timeParts.second].map((item) => String(item).padStart(2, "0")).join(":") + timeParts.fraction;
|
||||
|
||||
if (kind === "date") return dateText;
|
||||
if (kind === "time") return timeText;
|
||||
return `${dateText} ${timeText}`;
|
||||
if (kind === "time") return `${timeText}${offset}`;
|
||||
return `${dateText} ${timeText}${offset}`;
|
||||
}
|
||||
|
||||
function normalizeTemporalType(dataType: string | undefined): string {
|
||||
|
||||
Reference in new issue
Block a user