diff --git a/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts b/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts index 32f9f873d0aac..feb082c19606e 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts @@ -226,6 +226,11 @@ export class AdapterDateFnsBase return /a/.test(this.locale.formatLong!.time({ width: 'short' })); }; + // There is no notion of locale on the date in Date Fns. + public setLocaleToValue = (value: Date) => { + return value; + }; + public expandFormat = (format: string) => { const longFormatRegexp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g; diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 1245f256ea809..f260c405c55a3 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -165,15 +165,6 @@ export class AdapterDayjs implements MuiPickersAdapter { defaultDayjs.extend(customParseFormatPlugin); } - private setLocaleToValue = (value: Dayjs) => { - const expectedLocale = this.getCurrentLocaleCode(); - if (expectedLocale === value.locale()) { - return value; - } - - return value.locale(expectedLocale); - }; - private hasUTCPlugin = () => typeof defaultDayjs.utc !== 'undefined'; private hasTimezonePlugin = () => typeof defaultDayjs.tz !== 'undefined'; @@ -389,6 +380,15 @@ export class AdapterDayjs implements MuiPickersAdapter { return /A|a/.test(this.getLocaleFormats().LT || ''); }; + public setLocaleToValue = (value: Dayjs) => { + const expectedLocale = this.getCurrentLocaleCode(); + if (expectedLocale === value.locale()) { + return value; + } + + return value.locale(expectedLocale); + }; + public expandFormat = (format: string) => { const localeFormats = this.getLocaleFormats(); diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts index 3fd21f704315a..ca36d5d9af3b1 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts @@ -137,15 +137,6 @@ export class AdapterLuxon implements MuiPickersAdapter { this.formats = { ...defaultFormats, ...formats }; } - private setLocaleToValue = (value: DateTime) => { - const expectedLocale = this.getCurrentLocaleCode(); - if (expectedLocale === value.locale) { - return value; - } - - return value.setLocale(expectedLocale); - }; - public date = ( value?: T, timezone: PickersTimezone = 'default', @@ -210,6 +201,15 @@ export class AdapterLuxon implements MuiPickersAdapter { ); }; + public setLocaleToValue = (value: DateTime) => { + const expectedLocale = this.getCurrentLocaleCode(); + if (expectedLocale === value.locale) { + return value; + } + + return value.setLocale(expectedLocale); + }; + public expandFormat = (format: string) => { // Extract escaped section to avoid extending them const catchEscapedSectionsRegexp = /''|'(''|[^'])+('|$)|[^']*/g; diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts index 57ba9986e51f6..ad989fd73a8a6 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts @@ -143,15 +143,6 @@ export class AdapterMoment implements MuiPickersAdapter { this.formats = { ...defaultFormats, ...formats }; } - private setLocaleToValue = (value: Moment) => { - const expectedLocale = this.getCurrentLocaleCode(); - if (expectedLocale === value.locale()) { - return value; - } - - return value.locale(expectedLocale); - }; - private hasTimezonePlugin = () => typeof this.moment.tz !== 'undefined'; private createSystemDate = (value: string | undefined): Moment => { @@ -284,6 +275,15 @@ export class AdapterMoment implements MuiPickersAdapter { return /A|a/.test(defaultMoment.localeData(this.getCurrentLocaleCode()).longDateFormat('LT')); }; + public setLocaleToValue = (value: Moment) => { + const expectedLocale = this.getCurrentLocaleCode(); + if (expectedLocale === value.locale()) { + return value; + } + + return value.locale(expectedLocale); + }; + public expandFormat = (format: string) => { // @see https://github.com/moment/moment/blob/develop/src/lib/format/format.js#L6 const localFormattingTokens = /(\[[^[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})|./g; diff --git a/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx b/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx deleted file mode 100644 index 7fbd799e2208e..0000000000000 --- a/packages/x-date-pickers/src/DateCalendar/DateCalendar.spec.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import * as React from 'react'; -import moment, { Moment } from 'moment'; -import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; - -// External components are generic as well - - view="day" - views={['day']} - value={moment()} - minDate={moment()} - maxDate={moment()} - onChange={(date) => date?.format()} -/>; diff --git a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx index f1a173e452491..02c98ccdd935f 100644 --- a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx @@ -574,7 +574,7 @@ export function DayCalendar(inProps: DayCalendarP key={i.toString()} variant="caption" role="columnheader" - aria-label={utils.format(utils.addDays(startOfCurrentWeek, i), 'weekday')} + aria-label={utils.format(weekday, 'weekday')} className={classes.weekDayLabel} > {dayOfWeekFormatter(weekday)} diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx index bd049cba6fd98..53ff42ee719a0 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.spec.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import moment, { Moment } from 'moment'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; ; @@ -8,3 +9,13 @@ import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; ; ; + +// External components are generic as well + + view="day" + views={['day']} + value={moment()} + minDate={moment()} + maxDate={moment()} + onChange={(date) => date?.format()} +/>; diff --git a/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx index 6b4105506de25..5faff1878d13f 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/localization.DateCalendar.test.tsx @@ -1,11 +1,14 @@ import * as React from 'react'; import { expect } from 'chai'; -import { screen } from '@mui/internal-test-utils'; -import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; -import { createPickerRenderer, AdapterName } from 'test/utils/pickers'; -import { he } from 'date-fns/locale'; +import { screen, createRenderer } from '@mui/internal-test-utils'; +import { DateCalendar, dayCalendarClasses } from '@mui/x-date-pickers/DateCalendar'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { createPickerRenderer, AdapterName, availableAdapters } from 'test/utils/pickers'; +import { he, fr } from 'date-fns/locale'; import 'dayjs/locale/he'; import 'moment/locale/he'; +import 'dayjs/locale/fr'; +import 'moment/locale/fr'; const ADAPTERS_TO_USE: AdapterName[] = ['date-fns', 'dayjs', 'luxon', 'moment']; @@ -17,11 +20,33 @@ describe(' - localization', () => { adapterName, }); + const { render: renderWithoutWrapper } = createRenderer(); + it('should display correct week day labels in Hebrew locale ', () => { render(); expect(screen.getByText('א')).toBeVisible(); }); + + it('should correctly switch between locale with week starting in Monday and week starting in Sunday', () => { + const { setProps } = renderWithoutWrapper( + + + , + ); + + expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal( + 'Sunday', + ); + + setProps({ + adapterLocale: adapterName === 'date-fns' ? fr : 'fr', + }); + + expect(document.querySelector(`.${dayCalendarClasses.weekDayLabel}`)!.ariaLabel).to.equal( + 'lundi', + ); + }); }); }); }); diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.ts b/packages/x-date-pickers/src/internals/utils/date-utils.ts index 5f04069743a44..aca69fa39990d 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.ts @@ -203,6 +203,7 @@ export const getWeekdays = ( utils: MuiPickersAdapter, date: TDate, ) => { - const start = utils.startOfWeek(date); + const cleanDate = utils.setLocaleToValue(date); + const start = utils.startOfWeek(cleanDate); return [0, 1, 2, 3, 4, 5, 6].map((diff) => utils.addDays(start, diff)); }; diff --git a/packages/x-date-pickers/src/models/adapters.ts b/packages/x-date-pickers/src/models/adapters.ts index 17c58ab20d994..699ab8ed6d39d 100644 --- a/packages/x-date-pickers/src/models/adapters.ts +++ b/packages/x-date-pickers/src/models/adapters.ts @@ -236,6 +236,12 @@ export interface MuiPickersAdapter * @returns {boolean} `true` if the current locale is using 12 hours cycle. */ is12HourCycleInCurrentLocale(): boolean; + /** + * Set the locale of the adapter to the given value. + * @param {TDate} value The value to set the locale to. + * @returns {TDate} The value with the locale set. + */ + setLocaleToValue(value: TDate): TDate; /** * Create a format with no meta-token (e.g: `LLL` or `PP`). * @param {string} format The format to expand. diff --git a/test/utils/pickers/describeGregorianAdapter/testLocalization.ts b/test/utils/pickers/describeGregorianAdapter/testLocalization.ts index a63d1e0cf851d..b8db3f25f952b 100644 --- a/test/utils/pickers/describeGregorianAdapter/testLocalization.ts +++ b/test/utils/pickers/describeGregorianAdapter/testLocalization.ts @@ -4,13 +4,25 @@ import { cleanText } from 'test/utils/pickers'; import { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types'; import { TEST_DATE_ISO_STRING } from './describeGregorianAdapter.utils'; -export const testLocalization: DescribeGregorianAdapterTestSuite = ({ adapter }) => { +export const testLocalization: DescribeGregorianAdapterTestSuite = ({ + adapter, + adapterFr, + getLocaleFromDate, +}) => { const testDateIso = adapter.date(TEST_DATE_ISO_STRING)!; it('Method: formatNumber', () => { expect(adapter.formatNumber('1')).to.equal('1'); }); + it('Method: setLocaleToValue', () => { + if (getLocaleFromDate) { + const dateEn = adapter.date(); + const dateFr = adapterFr.setLocaleToValue(dateEn); + expect(getLocaleFromDate(dateFr)).to.equal('fr'); + } + }); + it('Method: expandFormat', () => { const testFormat = (formatKey: keyof AdapterFormats) => { const formatString = adapter.formats[formatKey];