Skip to content

Commit

Permalink
[pickers] Fix DateCalendar timezone management (#12321)
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasTy authored and web-flow committed Oct 25, 2024
1 parent eb0b462 commit 7d2c714
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 14 deletions.
18 changes: 9 additions & 9 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ coverage:
adapters:
target: 100%
paths:
- 'packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts'
- 'packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts'
- 'packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts'
- 'packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts'
- 'packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts'
- 'packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts'
- 'packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts'
- 'packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts'
- 'packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts'
- packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts
- packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts
- packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts
- packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts
- packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts
- packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts
- packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts
- packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts
- packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts
patch: off

comment: false
Original file line number Diff line number Diff line change
@@ -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('<DateRangeCalendar /> - 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 (
<React.Fragment>
<DateRangeCalendar timezone={timezone} calendars={1} />
<button onClick={() => setTimezone('America/New_York')}>Switch timezone</button>
</React.Fragment>
);
}
render(<DateCalendarWithControlledTimezone />);

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);
});
});
});
7 changes: 3 additions & 4 deletions packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,8 @@ export function DayCalendar<TDate extends PickerValidDate>(inProps: DayCalendarP
]);

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(
Expand All @@ -553,7 +552,7 @@ export function DayCalendar<TDate extends PickerValidDate>(inProps: DayCalendarP
nextMonth = utils.addMonths(nextMonth, 1);
}
return toDisplay;
}, [currentMonth, fixedWeekNumber, utils, timezone]);
}, [currentMonth, fixedWeekNumber, utils]);

return (
<PickersCalendarDayRoot role="grid" aria-labelledby={gridLabelId} className={classes.root}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,49 @@ describe('<DateCalendar /> - 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(<DateCalendar value={value} onChange={onChange} timezone="default" />);

fireEvent.click(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(adapter.lib === 'dayjs' ? 'UTC' : 'system');
expect(actualDate).toEqualDateTime(expectedDate);
});

it('should correctly render month days when timezone changes', () => {
function DateCalendarWithControlledTimezone() {
const [timezone, setTimezone] = React.useState('Europe/Paris');
return (
<React.Fragment>
<DateCalendar timezone={timezone} />
<button onClick={() => setTimezone('America/New_York')}>Switch timezone</button>
</React.Fragment>
);
}
render(<DateCalendarWithControlledTimezone />);

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);
});

TIMEZONE_TO_TEST.forEach((timezone) => {
describe(`Timezone: ${timezone}`, () => {
it('should use timezone prop for onChange when no value is provided', () => {
Expand Down
29 changes: 28 additions & 1 deletion packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const createCalendarStateReducer =
action:
| ReducerAction<'finishMonthSwitchingAnimation'>
| ReducerAction<'changeMonth', ChangeMonthPayload<TDate>>
| ReducerAction<'changeMonthTimezone', { newTimezone: string }>
| ReducerAction<'changeFocusedDay', ChangeFocusedDayPayload<TDate>>,
): CalendarState<TDate> => {
switch (action.type) {
Expand All @@ -54,6 +55,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,
Expand Down Expand Up @@ -149,7 +165,9 @@ export const useCalendarState = <TDate extends PickerValidDate>(
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, {
Expand All @@ -159,6 +177,15 @@ export const useCalendarState = <TDate extends PickerValidDate>(
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<TDate>) => {
dispatch({
Expand Down

0 comments on commit 7d2c714

Please sign in to comment.