diff --git a/core/src/components/datetime/test/manipulation.spec.ts b/core/src/components/datetime/test/manipulation.spec.ts index daebf7c33ea..d03cec325ba 100644 --- a/core/src/components/datetime/test/manipulation.spec.ts +++ b/core/src/components/datetime/test/manipulation.spec.ts @@ -558,6 +558,38 @@ describe('validateParts()', () => { }) ).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 }); }); + it('should not mutate the max parts when the day does not exist in the new month', () => { + // Selecting March 31 and then April on the month wheel produces April 31. + const maxParts = { month: 4, day: 20, year: 2024, hour: 23, minute: 59 }; + + expect(validateParts({ month: 4, day: 31, year: 2024, hour: 23, minute: 59 }, undefined, maxParts)).toEqual({ + month: 4, + day: 20, + year: 2024, + hour: 23, + minute: 59, + }); + expect(maxParts).toEqual({ month: 4, day: 20, year: 2024, hour: 23, minute: 59 }); + }); + it('should not mutate the min parts when the day does not exist in the new month', () => { + const minParts = { month: 3, day: 1, year: 2024, hour: 0, minute: 0 }; + + const result = validateParts({ month: 2, day: 31, year: 2024, hour: 0, minute: 0 }, minParts); + expect(result).toEqual({ month: 3, day: 1, year: 2024, hour: 0, minute: 0 }); + expect(minParts).toEqual({ month: 3, day: 1, year: 2024, hour: 0, minute: 0 }); + }); + it('should move the time back in bounds when the fixed day lands on the max day', () => { + // February 31 becomes February 29, which is the max day, so the max time applies. + expect( + validateParts({ month: 2, day: 31, year: 2024, hour: 23, minute: 0 }, undefined, { + month: 2, + day: 29, + year: 2024, + hour: 20, + minute: 0, + }) + ).toEqual({ month: 2, day: 29, year: 2024, hour: 20, minute: 0 }); + }); }); describe('getClosestValidDate()', () => { diff --git a/core/src/components/datetime/utils/manipulation.ts b/core/src/components/datetime/utils/manipulation.ts index 152f2f62fec..6c954efd251 100644 --- a/core/src/components/datetime/utils/manipulation.ts +++ b/core/src/components/datetime/utils/manipulation.ts @@ -342,7 +342,7 @@ export const validateParts = ( maxParts?: DatetimeParts ): DatetimeParts => { const { month, day, year } = parts; - const partsCopy = clampDate({ ...parts }, minParts, maxParts); + const dayInMonth = { ...parts }; const numDays = getNumDaysInMonth(month, year); @@ -351,11 +351,21 @@ export const validateParts = ( * is greater than the day we want * to set, update the DatetimeParts * day field to be the max days. + * This is done before clamping so that + * the fixed day is also checked against + * the min/max bounds. */ if (day !== null && numDays < day) { - partsCopy.day = numDays; + dayInMonth.day = numDays; } + /** + * clampDate can return the minParts or maxParts + * object itself, so copy the result to avoid + * changing the bounds when updating the time below. + */ + const partsCopy = { ...clampDate(dayInMonth, minParts, maxParts) }; + /** * If value is same day as min day, * make sure the time value is in bounds. @@ -391,7 +401,7 @@ export const validateParts = ( * If value is same day as max day, * make sure the time value is in bounds. */ - if (maxParts !== undefined && isSameDay(parts, maxParts)) { + if (maxParts !== undefined && isSameDay(partsCopy, maxParts)) { /** * If the hour is out of bounds, * update both the hour and minute.