fix(ui): clear miliseconds in date fields unless theyre explicitly provided in the display format (#12650)

Fixes https://github.com/payloadcms/payload/issues/12532

Normally we clear any values when picking a date such that your hour,
minutes and seconds are normalised to 0 unless specified. Equally when
you specify a time we will normalise seconds so that only minutes are
relevant as configured.

Miliseconds were never removed from the actual date value and whatever
milisecond the editor was in was that value that was being added.
There's this [abandoned
issue](https://github.com/Hacker0x01/react-datepicker/issues/1991) from
the UI library `react-datepicker` as it's not something configurable.

This fixes that problem by making sure that miliseconds are always 0
unless the `displayFormat` includes `SSS` as an intention to show and
customise them.

This also caused [issues with scheduled
jobs](https://github.com/payloadcms/payload/issues/12566) if things were
slightly out of order or not being scheduled in the expected time
interval.
This commit is contained in:
Paul
2025-06-03 02:44:52 -07:00
committed by GitHub
parent 0ceb96b12d
commit a9ff375cc0
4 changed files with 83 additions and 0 deletions

View File

@@ -111,6 +111,70 @@ describe('Date', () => {
await expect(dateField).toHaveValue('')
})
test('should clear miliseconds from dates with time', async () => {
await page.goto(url.create)
const dateField = page.locator('#field-default input')
await expect(dateField).toBeVisible()
// Fill in required fields, this is just to make sure saving is possible
await dateField.fill('02/07/2023')
const dateWithTz = page.locator('#field-dayAndTimeWithTimezone .react-datepicker-wrapper input')
await dateWithTz.fill('08/12/2027 10:00 AM')
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("London")`
await page.click(dropdownControlSelector)
await page.click(timezoneOptionSelector)
// Test the time field
const timeField = page.locator('#field-timeOnly input')
await timeField.fill('08/12/2027 10:00:00.123 AM')
await saveDocAndAssert(page)
const id = page.url().split('/').pop()
const { doc } = await client.findByID({ id: id!, auth: true, slug: 'date-fields' })
await expect(() => {
// Ensure that the time field does not contain milliseconds
expect(doc?.timeOnly).toContain('00:00.000Z')
}).toPass()
})
test("should keep miliseconds when they're provided in the date format", async () => {
await page.goto(url.create)
const dateField = page.locator('#field-default input')
await expect(dateField).toBeVisible()
// Fill in required fields, this is just to make sure saving is possible
await dateField.fill('02/07/2023')
const dateWithTz = page.locator('#field-dayAndTimeWithTimezone .react-datepicker-wrapper input')
await dateWithTz.fill('08/12/2027 10:00 AM')
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("London")`
await page.click(dropdownControlSelector)
await page.click(timezoneOptionSelector)
// Test the time field
const timeField = page.locator('#field-timeOnlyWithMiliseconds input')
await timeField.fill('6:00.00.625 PM')
await saveDocAndAssert(page)
const id = page.url().split('/').pop()
const { doc } = await client.findByID({ id: id!, auth: true, slug: 'date-fields' })
await expect(() => {
// Ensure that the time with miliseconds field contains the exact miliseconds specified
expect(doc?.timeOnlyWithMiliseconds).toContain('625Z')
}).toPass()
})
describe('localized dates', () => {
describe('EST', () => {
test.use({

View File

@@ -24,6 +24,16 @@ const DateFields: CollectionConfig = {
},
},
},
{
name: 'timeOnlyWithMiliseconds',
type: 'date',
admin: {
date: {
pickerAppearance: 'timeOnly',
displayFormat: 'h:mm.ss.SSS aa',
},
},
},
{
name: 'timeOnlyWithCustomFormat',
type: 'date',

View File

@@ -903,6 +903,7 @@ export interface DateField {
id: string;
default: string;
timeOnly?: string | null;
timeOnlyWithMiliseconds?: string | null;
timeOnlyWithCustomFormat?: string | null;
dayOnly?: string | null;
dayAndTime?: string | null;
@@ -2486,6 +2487,7 @@ export interface CustomRowIdSelect<T extends boolean = true> {
export interface DateFieldsSelect<T extends boolean = true> {
default?: T;
timeOnly?: T;
timeOnlyWithMiliseconds?: T;
timeOnlyWithCustomFormat?: T;
dayOnly?: T;
dayAndTime?: T;