Skip to content

Commit a9ff375

Browse files
authored
fix(ui): clear miliseconds in date fields unless theyre explicitly provided in the display format (#12650)
Fixes #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](Hacker0x01/react-datepicker#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](#12566) if things were slightly out of order or not being scheduled in the expected time interval.
1 parent 0ceb96b commit a9ff375

File tree

4 files changed

+83
-0
lines changed

4 files changed

+83
-0
lines changed

packages/ui/src/elements/DatePicker/DatePicker.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ const DatePicker: React.FC<Props> = (props) => {
6565
const tzOffset = incomingDate.getTimezoneOffset() / 60
6666
newDate.setHours(12 - tzOffset, 0)
6767
}
68+
69+
if (newDate instanceof Date && !dateFormat.includes('SSS')) {
70+
// Unless the dateFormat includes milliseconds, set milliseconds to 0
71+
// This is to ensure that the timestamp is consistent with the displayFormat
72+
newDate.setMilliseconds(0)
73+
}
74+
6875
if (typeof onChangeFromProps === 'function') {
6976
onChangeFromProps(newDate)
7077
}

test/fields/collections/Date/e2e.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,70 @@ describe('Date', () => {
111111
await expect(dateField).toHaveValue('')
112112
})
113113

114+
test('should clear miliseconds from dates with time', async () => {
115+
await page.goto(url.create)
116+
const dateField = page.locator('#field-default input')
117+
await expect(dateField).toBeVisible()
118+
// Fill in required fields, this is just to make sure saving is possible
119+
await dateField.fill('02/07/2023')
120+
const dateWithTz = page.locator('#field-dayAndTimeWithTimezone .react-datepicker-wrapper input')
121+
122+
await dateWithTz.fill('08/12/2027 10:00 AM')
123+
124+
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
125+
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("London")`
126+
127+
await page.click(dropdownControlSelector)
128+
await page.click(timezoneOptionSelector)
129+
130+
// Test the time field
131+
const timeField = page.locator('#field-timeOnly input')
132+
await timeField.fill('08/12/2027 10:00:00.123 AM')
133+
134+
await saveDocAndAssert(page)
135+
136+
const id = page.url().split('/').pop()
137+
138+
const { doc } = await client.findByID({ id: id!, auth: true, slug: 'date-fields' })
139+
140+
await expect(() => {
141+
// Ensure that the time field does not contain milliseconds
142+
expect(doc?.timeOnly).toContain('00:00.000Z')
143+
}).toPass()
144+
})
145+
146+
test("should keep miliseconds when they're provided in the date format", async () => {
147+
await page.goto(url.create)
148+
const dateField = page.locator('#field-default input')
149+
await expect(dateField).toBeVisible()
150+
// Fill in required fields, this is just to make sure saving is possible
151+
await dateField.fill('02/07/2023')
152+
const dateWithTz = page.locator('#field-dayAndTimeWithTimezone .react-datepicker-wrapper input')
153+
154+
await dateWithTz.fill('08/12/2027 10:00 AM')
155+
156+
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
157+
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("London")`
158+
159+
await page.click(dropdownControlSelector)
160+
await page.click(timezoneOptionSelector)
161+
162+
// Test the time field
163+
const timeField = page.locator('#field-timeOnlyWithMiliseconds input')
164+
await timeField.fill('6:00.00.625 PM')
165+
166+
await saveDocAndAssert(page)
167+
168+
const id = page.url().split('/').pop()
169+
170+
const { doc } = await client.findByID({ id: id!, auth: true, slug: 'date-fields' })
171+
172+
await expect(() => {
173+
// Ensure that the time with miliseconds field contains the exact miliseconds specified
174+
expect(doc?.timeOnlyWithMiliseconds).toContain('625Z')
175+
}).toPass()
176+
})
177+
114178
describe('localized dates', () => {
115179
describe('EST', () => {
116180
test.use({

test/fields/collections/Date/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ const DateFields: CollectionConfig = {
2424
},
2525
},
2626
},
27+
{
28+
name: 'timeOnlyWithMiliseconds',
29+
type: 'date',
30+
admin: {
31+
date: {
32+
pickerAppearance: 'timeOnly',
33+
displayFormat: 'h:mm.ss.SSS aa',
34+
},
35+
},
36+
},
2737
{
2838
name: 'timeOnlyWithCustomFormat',
2939
type: 'date',

test/fields/payload-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ export interface DateField {
903903
id: string;
904904
default: string;
905905
timeOnly?: string | null;
906+
timeOnlyWithMiliseconds?: string | null;
906907
timeOnlyWithCustomFormat?: string | null;
907908
dayOnly?: string | null;
908909
dayAndTime?: string | null;
@@ -2486,6 +2487,7 @@ export interface CustomRowIdSelect<T extends boolean = true> {
24862487
export interface DateFieldsSelect<T extends boolean = true> {
24872488
default?: T;
24882489
timeOnly?: T;
2490+
timeOnlyWithMiliseconds?: T;
24892491
timeOnlyWithCustomFormat?: T;
24902492
dayOnly?: T;
24912493
dayAndTime?: T;

0 commit comments

Comments
 (0)