From 67edee9277025e773c058d3f04af62e05f47d09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Delgado?= Date: Thu, 25 Jun 2026 08:01:15 +0200 Subject: [PATCH 1/2] Convert date fields to UTC on the client Datetime values lost their timezone on the server-side read/write round-trip because the form input has no offset and Vercel runs in UTC: a user in Madrid typing 14:30 ended up stored as 14:30Z. The fix moves the timezone-aware conversion to the browser, where the local offset is known. Edit and view components now treat the stored wall-clock as UTC and shift to local for display; on submit the local input is converted back to a UTC wall-clock. The server keeps doing exactly what it did before (naive parse and format) and, being in UTC, that round-trips correctly. Closes hunvreus/pagescms#247. --- fields/core/date/edit-component.tsx | 20 ++++++++++++++++++-- fields/core/date/index.ts | 7 ++++--- fields/core/date/view-component.tsx | 9 ++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/fields/core/date/edit-component.tsx b/fields/core/date/edit-component.tsx index 4c7657e98..888241efc 100644 --- a/fields/core/date/edit-component.tsx +++ b/fields/core/date/edit-component.tsx @@ -4,6 +4,22 @@ import { forwardRef } from "react"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; +// Server passes/receives UTC wall-clock strings (yyyy-MM-dd'T'HH:mm). Browser converts to/from local. +const toLocal = (value: string, time: boolean) => { + if (!time) return value; + if (!value) return ""; + + const date = new Date(`${value}Z`); + return date.toLocaleString("sv-SE").replace(" ", "T").slice(0, 16); +}; +const toUtc = (value: string, time: boolean) => { + if (!time) return value; + if (!value) return ""; + + const date = new Date(value); + return date.toISOString().slice(0, 16); +}; + const EditComponent = forwardRef((props: any, ref: React.Ref) => { const { field, value, onChange } = props; @@ -14,8 +30,8 @@ const EditComponent = forwardRef((props: any, ref: React.Ref) step={field?.options?.step ?? undefined} ref={ref} type={field?.options?.time ? "datetime-local" : "date"} - value={value} - onChange={onChange} + value={toLocal(value, field?.options?.time)} + onChange={(e) => onChange(toUtc(e.target.value, field?.options?.time))} className={cn("w-auto text-base", field?.readonly && "focus-visible:border-input focus-visible:ring-0")} readOnly={field?.readonly} /> diff --git a/fields/core/date/index.ts b/fields/core/date/index.ts index 531d3b1cd..280024068 100644 --- a/fields/core/date/index.ts +++ b/fields/core/date/index.ts @@ -5,9 +5,10 @@ import { ViewComponent } from "./view-component"; import { parse, format, isValid, isBefore, isAfter } from "date-fns"; const defaultValue = (field: Field) => { - const inputType = field?.options?.time ? "datetime-local" : "date"; - const inputFormat = inputType === "datetime-local" ? "yyyy-MM-dd'T'HH:mm" : "yyyy-MM-dd"; - return format(new Date(), inputFormat); + const inputType = field?.options?.time ? "datetime-local" : "date"; + return (inputType === "datetime-local") + ? new Date().toISOString().slice(0, 16) + : format(new Date(), "yyyy-MM-dd"); }; const read = (value: any, field: Field) => { diff --git a/fields/core/date/view-component.tsx b/fields/core/date/view-component.tsx index 3d52bd765..91982ce7a 100644 --- a/fields/core/date/view-component.tsx +++ b/fields/core/date/view-component.tsx @@ -16,11 +16,14 @@ const ViewComponent = ({ const firstValue = Array.isArray(value) ? value[0] : value; if (firstValue == null) return null; const extraValuesCount = Array.isArray(value) ? value.length - 1 : 0; - const inputFormat = field.options?.time ? "yyyy-MM-dd'T'HH:mm" : "yyyy-MM-dd"; - const outputFormat = field.options?.time ? "MMM d, yyyy - HH:mm" : "MMM d, yyyy"; + const inputType = field?.options?.time ? "datetime-local" : "date"; + const inputFormat = inputType === "datetime-local" ? "yyyy-MM-dd'T'HH:mm" : "yyyy-MM-dd"; + const outputFormat = inputType === "datetime-local" ? "MMM d, yyyy - HH:mm" : "MMM d, yyyy"; const formatDate = (date: string) => { - const parsedDate = parse(date, inputFormat, new Date()); + const parsedDate = inputType === "datetime-local" + ? new Date(`${date}Z`) + : parse(date, inputFormat, new Date()); if (!isValid(parsedDate)) { console.warn(`Date for field '${field.name}' is saved in the wrong format or invalid: ${date}.`); return null; From 0987d670f5dc27bf548c42fd01e0e9afbbc0b406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Jim=C3=A9nez=20Delgado?= Date: Thu, 25 Jun 2026 09:13:50 +0200 Subject: [PATCH 2/2] Drop redundant inputFormat var in date view-component --- fields/core/date/view-component.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fields/core/date/view-component.tsx b/fields/core/date/view-component.tsx index 91982ce7a..1bd412a98 100644 --- a/fields/core/date/view-component.tsx +++ b/fields/core/date/view-component.tsx @@ -17,13 +17,12 @@ const ViewComponent = ({ if (firstValue == null) return null; const extraValuesCount = Array.isArray(value) ? value.length - 1 : 0; const inputType = field?.options?.time ? "datetime-local" : "date"; - const inputFormat = inputType === "datetime-local" ? "yyyy-MM-dd'T'HH:mm" : "yyyy-MM-dd"; const outputFormat = inputType === "datetime-local" ? "MMM d, yyyy - HH:mm" : "MMM d, yyyy"; const formatDate = (date: string) => { const parsedDate = inputType === "datetime-local" ? new Date(`${date}Z`) - : parse(date, inputFormat, new Date()); + : parse(date, "yyyy-MM-dd", new Date()); if (!isValid(parsedDate)) { console.warn(`Date for field '${field.name}' is saved in the wrong format or invalid: ${date}.`); return null;