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()}`; } }