diff --git a/docs/src/content/pages/fields/datetime.mdoc b/docs/src/content/pages/fields/datetime.mdoc
index f8c1f07d3..a74b714aa 100644
--- a/docs/src/content/pages/fields/datetime.mdoc
+++ b/docs/src/content/pages/fields/datetime.mdoc
@@ -4,7 +4,12 @@ summary: The datetime field stores a Datetime string.
---
{% field-demo field="datetime" /%}
-The `datetime` field stores a Datetime string, collected from an `` form field.
+The `datetime` field stores an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) formatted datetime string in UTC time (i.e. YYYY-MM-DDTHH:MM:SS.000Z), collected from an `` form field.
+
+{% aside icon="☝️" %}
+Datetimes are stored in UTC time, but displayed/edited in the user's local timezone.
+{% /aside %}
+
## Usage example
diff --git a/packages/keystatic/src/form/fields/datetime/index.tsx b/packages/keystatic/src/form/fields/datetime/index.tsx
index c249b4ef7..2326df091 100644
--- a/packages/keystatic/src/form/fields/datetime/index.tsx
+++ b/packages/keystatic/src/form/fields/datetime/index.tsx
@@ -39,13 +39,10 @@ export function datetime({
return null;
}
if (typeof defaultValue === 'string') {
- return defaultValue;
+ return new Date(defaultValue).toISOString();
}
if (defaultValue.kind === 'now') {
- const now = new Date();
- return new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000)
- .toISOString()
- .slice(0, -8);
+ return new Date().toISOString();
}
return null;
},
@@ -54,19 +51,16 @@ export function datetime({
return null;
}
if (value instanceof Date) {
- return value.toISOString().slice(0, -8);
+ return value.toISOString();
}
if (typeof value !== 'string') {
throw new FieldDataError('Must be a string or date');
}
- return value;
+ return new Date(value).toISOString();
},
serialize(value) {
if (value === null) return { value: undefined };
- const date = new Date(value + 'Z');
- date.toJSON = () => date.toISOString().slice(0, -8);
- date.toString = () => date.toISOString().slice(0, -8);
- return { value: date };
+ return { value: new Date(value).toISOString() };
},
validate(value) {
const message = validateDatetime(validation, value, label);
diff --git a/packages/keystatic/src/form/fields/datetime/ui.tsx b/packages/keystatic/src/form/fields/datetime/ui.tsx
index 43f1f579f..232cb0049 100644
--- a/packages/keystatic/src/form/fields/datetime/ui.tsx
+++ b/packages/keystatic/src/form/fields/datetime/ui.tsx
@@ -5,6 +5,14 @@ import { useReducer } from 'react';
import { validateDatetime } from './validateDatetime';
import { FormFieldInputProps } from '../../api';
+function convertUTCToLocal(datetime: string | null): string {
+ if (!datetime) return '';
+ const date = new Date(datetime);
+ const offset = date.getTimezoneOffset();
+ const localDate = new Date(date.getTime() - offset * 60 * 1000);
+ return localDate.toISOString().slice(0, 16);
+}
+
export function DatetimeFieldInput(
props: FormFieldInputProps & {
label: string;
@@ -23,7 +31,7 @@ export function DatetimeFieldInput(
props.onChange(val === '' ? null : val);
}}
autoFocus={props.autoFocus}
- value={props.value === null ? '' : props.value}
+ value={convertUTCToLocal(props.value)}
onBlur={onBlur}
isRequired={props.validation?.isRequired}
errorMessage={
diff --git a/packages/keystatic/src/form/fields/datetime/validateDatetime.tsx b/packages/keystatic/src/form/fields/datetime/validateDatetime.tsx
index e62028cb4..7435ce4e4 100644
--- a/packages/keystatic/src/form/fields/datetime/validateDatetime.tsx
+++ b/packages/keystatic/src/form/fields/datetime/validateDatetime.tsx
@@ -3,7 +3,7 @@ export function validateDatetime(
value: string | null,
label: string
) {
- if (value !== null && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) {
+ if (value !== null && isNaN(Date.parse(value))) {
return `${label} is not a valid datetime`;
}
@@ -12,15 +12,16 @@ export function validateDatetime(
}
if ((validation?.min || validation?.max) && value !== null) {
const datetime = new Date(value);
+ const utcDatetime = new Date(datetime.getTime() - datetime.getTimezoneOffset() * 60000);
if (validation?.min !== undefined) {
const min = new Date(validation.min);
- if (datetime < min) {
+ if (utcDatetime < min) {
return `${label} must be after ${min.toISOString()}`;
}
}
if (validation?.max !== undefined) {
const max = new Date(validation.max);
- if (datetime > max) {
+ if (utcDatetime > max) {
return `${label} must be no later than ${max.toISOString()}`;
}
}