From 2e1226fe054f782cfe47268ebb4ac415a1b3969d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:45:13 +0100 Subject: [PATCH 1/7] Add datetime format --- .../src/dataform-controls/datetime.tsx | 9 ++- .../dataviews/src/field-types/datetime.tsx | 49 +++++++++++++--- .../src/stories/field-types.story.tsx | 56 ++++++++++++++++++- packages/dataviews/src/types/field-api.ts | 23 +++++++- packages/dataviews/src/types/private.ts | 2 + 5 files changed, 124 insertions(+), 15 deletions(-) diff --git a/packages/dataviews/src/dataform-controls/datetime.tsx b/packages/dataviews/src/dataform-controls/datetime.tsx index 74723ecd9f1d80..2e89b4eab9dedf 100644 --- a/packages/dataviews/src/dataform-controls/datetime.tsx +++ b/packages/dataviews/src/dataform-controls/datetime.tsx @@ -18,7 +18,7 @@ import { getSettings } from '@wordpress/date'; /** * Internal dependencies */ -import type { DataFormControlProps } from '../types'; +import type { DataFormControlProps, FormatDatetime } from '../types'; import { OPERATOR_IN_THE_PAST, OPERATOR_OVER } from '../constants'; import RelativeDateControl from './utils/relative-date-control'; import getCustomValidity from './utils/get-custom-validity'; @@ -147,9 +147,12 @@ function CalendarDateTimeControl< Item >( { [ onChangeCallback ] ); + const { format: fieldFormat } = field; + const weekStartsOn = + ( fieldFormat as FormatDatetime ).weekStartsOn ?? + getSettings().l10n.startOfWeek; const { timezone: { string: timezoneString }, - l10n: { startOfWeek }, } = getSettings(); const displayLabel = @@ -176,7 +179,7 @@ function CalendarDateTimeControl< Item >( { month={ calendarMonth } onMonthChange={ setCalendarMonth } timeZone={ timezoneString || undefined } - weekStartsOn={ startOfWeek } + weekStartsOn={ weekStartsOn } /> { /* Manual datetime input */ } ) { return null; } - try { - const dateValue = parseDateTime( value ); - return dateValue?.toLocaleString(); - } catch ( error ) { - return null; + // If the field type is date, we've already normalized the format, + // and so it's safe to tell TypeScript to trust us ("as Required"). + // + // There're no runtime paths where this render function is called with a non-date field, + // but TypeScript is unable to infer this, hence the type assertion. + let format: Required< FormatDatetime >; + if ( field.type !== 'datetime' ) { + format = getFormat( {} as Field< any > ); + } else { + format = field.format as Required< FormatDatetime >; } + + return dateI18n( format.datetime, getDate( value ) ); } const sort = ( a: any, b: any, direction: SortDirection ) => { @@ -43,6 +60,22 @@ const sort = ( a: any, b: any, direction: SortDirection ) => { return direction === 'asc' ? timeA - timeB : timeB - timeA; }; +function getFormat< Item >( field: Field< Item > ): Required< FormatDatetime > { + const fieldFormat = field.format as FormatDatetime | undefined; + return { + datetime: + fieldFormat?.datetime !== undefined && + typeof fieldFormat.datetime === 'string' + ? fieldFormat.datetime + : getSettings().formats.datetime, + weekStartsOn: + fieldFormat?.weekStartsOn !== undefined && + DAYS_OF_WEEK.includes( fieldFormat?.weekStartsOn ) + ? fieldFormat.weekStartsOn + : getSettings().l10n.startOfWeek, + }; +} + export default { type: 'datetime', render, @@ -70,7 +103,7 @@ export default { OPERATOR_IN_THE_PAST, OPERATOR_OVER, ], - getFormat: () => ( {} ), + getFormat, validate: { required: isValidRequired, elements: isValidElements, diff --git a/packages/dataviews/src/stories/field-types.story.tsx b/packages/dataviews/src/stories/field-types.story.tsx index 031af1b0f4fd2b..b1bd1595283793 100644 --- a/packages/dataviews/src/stories/field-types.story.tsx +++ b/packages/dataviews/src/stories/field-types.story.tsx @@ -890,13 +890,39 @@ export const DateTimeComponent = ( { type, Edit, asyncElements, + formatDatetime, + formatWeekStartsOn, }: { type: PanelTypes; Edit: ControlTypes; asyncElements: boolean; + formatDatetime?: string; + formatWeekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; } ) => { - const datetimeFields = fields.filter( ( field ) => - field.id.startsWith( 'datetime' ) + const datetimeFields = useMemo( + () => + fields + .filter( ( field ) => field.id.startsWith( 'datetime' ) ) + .map( ( field ) => { + if ( formatDatetime || formatWeekStartsOn !== undefined ) { + const format: { + datetime?: string; + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; + } = {}; + if ( formatDatetime ) { + format.datetime = formatDatetime; + } + if ( formatWeekStartsOn !== undefined ) { + format.weekStartsOn = formatWeekStartsOn; + } + return { + ...field, + format, + }; + } + return field; + } ), + [ fields, formatDatetime, formatWeekStartsOn ] ); return ( @@ -909,6 +935,32 @@ export const DateTimeComponent = ( { ); }; DateTimeComponent.storyName = 'datetime'; +DateTimeComponent.args = { + formatDatetime: '', + formatWeekStartsOn: undefined, +}; +DateTimeComponent.argTypes = { + formatDatetime: { + control: 'text', + description: + 'Custom PHP date format string (e.g., "M j, Y g:i a" for "Jan 1, 2021 2:30 pm"). Leave empty to use WordPress default.', + }, + formatWeekStartsOn: { + control: 'select', + options: { + Default: undefined, + Sunday: 0, + Monday: 1, + Tuesday: 2, + Wednesday: 3, + Thursday: 4, + Friday: 5, + Saturday: 6, + }, + description: + 'Day that the week starts on. Leave as Default to use WordPress default.', + }, +}; export const DateComponent = ( { type, diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index a65c9253a4499c..e2168779974f6c 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -273,13 +273,26 @@ export type Field< Item > = { /** * Display format configuration for fields. */ - format?: FormatDate | FormatNumber | FormatInteger; + format?: FormatDate | FormatDatetime | FormatNumber | FormatInteger; +}; + +/** + * Format for datetime fields: + * + * - datetime: the format string (e.g., "M j, Y g:i a" for "Jan 1, 2021 2:30 pm"). + * - weekStartsOn: to specify the first day of the week ('sunday', 'monday', etc.). + * + * If not provided, defaults to WordPress date format settings. + */ +export type FormatDatetime = { + datetime?: string; + weekStartsOn?: DayNumber; }; /** * Format for date fields: * - * - date: the format string (e.g., 'F j, Y' for WordPress default format like 'March 10, 2023') + * - date: the format string (e.g., 'F j, Y' for 'March 10, 2023') * - weekStartsOn: to specify the first day of the week ('sunday', 'monday', etc.). * * If not provided, defaults to WordPress date format settings. @@ -333,6 +346,11 @@ type NormalizedFieldBase< Item > = Omit< Field< Item >, 'Edit' | 'isValid' > & { format: {}; }; +export type NormalizedFieldDatetime< Item > = NormalizedFieldBase< Item > & { + type: 'datetime'; + format: Required< FormatDatetime >; +}; + export type NormalizedFieldDate< Item > = NormalizedFieldBase< Item > & { type: 'date'; format: Required< FormatDate >; @@ -351,6 +369,7 @@ export type NormalizedFieldInteger< Item > = NormalizedFieldBase< Item > & { export type NormalizedField< Item > = | NormalizedFieldBase< Item > | NormalizedFieldDate< Item > + | NormalizedFieldDatetime< Item > | NormalizedFieldNumber< Item > | NormalizedFieldInteger< Item >; diff --git a/packages/dataviews/src/types/private.ts b/packages/dataviews/src/types/private.ts index d00bb13be06cf7..d4b9d6b3c8c928 100644 --- a/packages/dataviews/src/types/private.ts +++ b/packages/dataviews/src/types/private.ts @@ -5,6 +5,7 @@ import type { CustomValidator, Field, FormatDate, + FormatDatetime, FormatInteger, FormatNumber, NormalizedField, @@ -26,6 +27,7 @@ export type FieldType< Item > = Pick< ) => | Record< string, any > | Required< FormatDate > + | Required< FormatDatetime > | Required< FormatNumber > | Required< FormatInteger >; validate: { From 12b1ddff8b51a0c7e482e1d146257498f8cc8daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:02:44 +0100 Subject: [PATCH 2/7] Fix comment --- packages/dataviews/src/types/field-api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/types/field-api.ts b/packages/dataviews/src/types/field-api.ts index e2168779974f6c..50a96cebe0a4ce 100644 --- a/packages/dataviews/src/types/field-api.ts +++ b/packages/dataviews/src/types/field-api.ts @@ -280,7 +280,7 @@ export type Field< Item > = { * Format for datetime fields: * * - datetime: the format string (e.g., "M j, Y g:i a" for "Jan 1, 2021 2:30 pm"). - * - weekStartsOn: to specify the first day of the week ('sunday', 'monday', etc.). + * - weekStartsOn: to specify the first day of the week (0 for 'sunday', 1 for 'monday', etc.). * * If not provided, defaults to WordPress date format settings. */ @@ -293,7 +293,7 @@ export type FormatDatetime = { * Format for date fields: * * - date: the format string (e.g., 'F j, Y' for 'March 10, 2023') - * - weekStartsOn: to specify the first day of the week ('sunday', 'monday', etc.). + * - weekStartsOn: to specify the first day of the week (0 for 'sunday', 1 for 'monday', etc.). * * If not provided, defaults to WordPress date format settings. */ From 6e222c90f190eb5466d3ea8e7224b5ecbe3bdd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:11:14 +0100 Subject: [PATCH 3/7] Filters use format --- .../dataviews/src/components/dataviews-filters/filter.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/components/dataviews-filters/filter.tsx b/packages/dataviews/src/components/dataviews-filters/filter.tsx index c63618bb0aa0bc..066eeb166fa9f5 100644 --- a/packages/dataviews/src/components/dataviews-filters/filter.tsx +++ b/packages/dataviews/src/components/dataviews-filters/filter.tsx @@ -37,6 +37,7 @@ import type { Operator, Option, View, + NormalizedFieldDatetime, } from '../../types'; import useElements from '../../hooks/use-elements'; import parseDateTime from '../../field-types/utils/parse-date-time'; @@ -223,7 +224,11 @@ export default function Filter( { try { const dateValue = parseDateTime( label ); if ( dateValue !== null ) { - label = dateValue.toLocaleString(); + label = dateI18n( + ( field as NormalizedFieldDatetime< any > ).format + .datetime, + getDate( label ) + ); } } catch ( e ) { label = filterInView.value; From 4c730f6ca6ea9428eb5588b5c0525bd0261b545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:22:36 +0100 Subject: [PATCH 4/7] Remove space between hour and minutes --- packages/date/CHANGELOG.md | 4 ++++ packages/date/src/index.ts | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/date/CHANGELOG.md b/packages/date/CHANGELOG.md index fc2e4fa39d126f..73833222c78f2b 100644 --- a/packages/date/CHANGELOG.md +++ b/packages/date/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## Bug Fixes + +- Fixed incorrect spacing for the time format. It was `g: i` (`14: 30`), and it's now `g:i` (`14:30`). See [#73924](https://github.com/WordPress/gutenberg/pull/73924). + ## 5.36.0 (2025-11-26) ### Bug Fixes diff --git a/packages/date/src/index.ts b/packages/date/src/index.ts index b7794f4b6b7870..60f7d77c9b04b7 100644 --- a/packages/date/src/index.ts +++ b/packages/date/src/index.ts @@ -86,10 +86,10 @@ let settings: DateSettings = { startOfWeek: 0, }, formats: { - time: 'g: i a', + time: 'g:i a', date: 'F j, Y', - datetime: 'F j, Y g: i a', - datetimeAbbreviated: 'M j, Y g: i a', + datetime: 'F j, Y g:i a', + datetimeAbbreviated: 'M j, Y g:i a', }, timezone: { offset: 0, offsetFormatted: '0', string: '', abbr: '' }, }; From bbc5b980f5a29c3a7e80960ce0f6082f008132d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:24:52 +0100 Subject: [PATCH 5/7] Changelog for dataviews --- packages/dataviews/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index b42bd878128e0e..2347af0582a125 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -20,6 +20,7 @@ - Field API: move validation to the field type. [#73642](https://github.com/WordPress/gutenberg/pull/73642) - DataForm: add support for `min`/`max` and `minLength`/`maxLength` validation for relevant controls. [#73465](https://github.com/WordPress/gutenberg/pull/73465) - Field API: display formats for `number` and `integer` types. [#73644](https://github.com/WordPress/gutenberg/pull/73644) +- Field API: add display format for `datetime` type. [#73924](https://github.com/WordPress/gutenberg/pull/73924) - DataViews: Update padding to 24px for consistency. [#73334](https://github.com/WordPress/gutenberg/pull/73334) - DataViews: Simplify list layout field color styles. [#73884](https://github.com/WordPress/gutenberg/pull/73884) From 830632f0a16a4749bcda679ba52ec266cd217b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:58:08 +0100 Subject: [PATCH 6/7] Update comment --- packages/dataviews/src/field-types/datetime.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/field-types/datetime.tsx b/packages/dataviews/src/field-types/datetime.tsx index d39d9da6f5321d..5737e83f4d1ee8 100644 --- a/packages/dataviews/src/field-types/datetime.tsx +++ b/packages/dataviews/src/field-types/datetime.tsx @@ -38,10 +38,10 @@ function render( { item, field }: DataViewRenderFieldProps< any > ) { return null; } - // If the field type is date, we've already normalized the format, + // If the field type is datetime, we've already normalized the format, // and so it's safe to tell TypeScript to trust us ("as Required"). // - // There're no runtime paths where this render function is called with a non-date field, + // There're no runtime paths where this render function is called with a non-datetime field, // but TypeScript is unable to infer this, hence the type assertion. let format: Required< FormatDatetime >; if ( field.type !== 'datetime' ) { From 9be840351fa341b447553044a3469d1bacbfa8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:59:19 +0100 Subject: [PATCH 7/7] Make date and datetime similar --- packages/dataviews/src/field-types/date.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataviews/src/field-types/date.tsx b/packages/dataviews/src/field-types/date.tsx index 6da753fed35718..9c41bb79a252a4 100644 --- a/packages/dataviews/src/field-types/date.tsx +++ b/packages/dataviews/src/field-types/date.tsx @@ -51,7 +51,7 @@ function render( { item, field }: DataViewRenderFieldProps< any > ) { } const value = field.getValue( { item } ); - if ( ! value ) { + if ( [ '', undefined, null ].includes( value ) ) { return ''; }