diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx new file mode 100644 index 000000000000..760f9275f3d6 --- /dev/null +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { screen, fireEvent } from '@mui-internal/test-utils'; +import { describeAdapters } from 'test/utils/pickers'; +import { DateRangeCalendar } from './DateRangeCalendar'; + +describe(' - Timezone', () => { + describeAdapters('Timezone prop', DateRangeCalendar, ({ adapter, render }) => { + if (!adapter.isTimezoneCompatible) { + return; + } + + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + + ); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); + }); +}); diff --git a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx index 64bfdc0b1e8e..ee69bf230153 100644 --- a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx @@ -533,9 +533,8 @@ export function DayCalendar(inProps: DayCalendarProps) { ]); const weeksToDisplay = React.useMemo(() => { - const currentMonthWithTimezone = utils.setTimezone(currentMonth, timezone); - const toDisplay = utils.getWeekArray(currentMonthWithTimezone); - let nextMonth = utils.addMonths(currentMonthWithTimezone, 1); + const toDisplay = utils.getWeekArray(currentMonth); + let nextMonth = utils.addMonths(currentMonth, 1); while (fixedWeekNumber && toDisplay.length < fixedWeekNumber) { const additionalWeeks = utils.getWeekArray(nextMonth); const hasCommonWeek = utils.isSameDay( @@ -552,7 +551,7 @@ export function DayCalendar(inProps: DayCalendarProps) { nextMonth = utils.addMonths(nextMonth, 1); } return toDisplay; - }, [currentMonth, fixedWeekNumber, utils, timezone]); + }, [currentMonth, fixedWeekNumber, utils]); return ( diff --git a/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx index 261af345fe21..43b4ba04ebff 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx @@ -29,6 +29,49 @@ describe(' - Timezone', () => { expect(actualDate).toEqualDateTime(expectedDate); }); + it('should use "default" timezone for onChange when provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-25T15:30'); + + render(); + + userEvent.mousePress(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(value, 25); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal('system'); + expect(actualDate).toEqualDateTime(expectedDate); + }); + + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + + ); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + userEvent.mousePress(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); + TIMEZONE_TO_TEST.forEach((timezone) => { describe(`Timezone: ${timezone}`, () => { it('should use timezone prop for onChange when no value is provided', () => { diff --git a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx index 440fa628f4dd..3ae9ee25deb6 100644 --- a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx +++ b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx @@ -42,6 +42,7 @@ export const createCalendarStateReducer = action: | ReducerAction<'finishMonthSwitchingAnimation'> | ReducerAction<'changeMonth', ChangeMonthPayload> + | ReducerAction<'changeMonthTimezone', { newTimezone: string }> | ReducerAction<'changeFocusedDay', ChangeFocusedDayPayload>, ): CalendarState => { switch (action.type) { @@ -53,6 +54,21 @@ export const createCalendarStateReducer = isMonthSwitchingAnimating: !reduceAnimations, }; + case 'changeMonthTimezone': { + const newTimezone = action.newTimezone; + if (utils.getTimezone(state.currentMonth) === newTimezone) { + return state; + } + let newCurrentMonth = utils.setTimezone(state.currentMonth, newTimezone); + if (utils.getMonth(newCurrentMonth) !== utils.getMonth(state.currentMonth)) { + newCurrentMonth = utils.setMonth(newCurrentMonth, utils.getMonth(state.currentMonth)); + } + return { + ...state, + currentMonth: newCurrentMonth, + }; + } + case 'finishMonthSwitchingAnimation': return { ...state, @@ -156,7 +172,9 @@ export const useCalendarState = (params: UseCalendarState granularity: SECTION_TYPE_GRANULARITY.day, }); }, - [], // eslint-disable-line react-hooks/exhaustive-deps + // We want the `referenceDate` to update on prop and `timezone` change (https://github.com/mui/mui-x/issues/10804) + // eslint-disable-next-line react-hooks/exhaustive-deps + [referenceDateProp, timezone], ); const [calendarState, dispatch] = React.useReducer(reducerFn, { @@ -166,6 +184,15 @@ export const useCalendarState = (params: UseCalendarState slideDirection: 'left', }); + // Ensure that `calendarState.currentMonth` timezone is updated when `referenceDate` (or timezone changes) + // https://github.com/mui/mui-x/issues/10804 + React.useEffect(() => { + dispatch({ + type: 'changeMonthTimezone', + newTimezone: utils.getTimezone(referenceDate), + }); + }, [referenceDate, utils]); + const handleChangeMonth = React.useCallback( (payload: ChangeMonthPayload) => { dispatch({