diff --git a/packages/@internationalized/date/src/queries.ts b/packages/@internationalized/date/src/queries.ts index fc95a2f7634..732594d729d 100644 --- a/packages/@internationalized/date/src/queries.ts +++ b/packages/@internationalized/date/src/queries.ts @@ -223,6 +223,7 @@ export function endOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayO } const cachedRegions = new Map(); +const cachedWeekInfo = new Map(); function getRegion(locale: string): string | undefined { // If the Intl.Locale API is available, use it to get the region for the locale. @@ -251,8 +252,47 @@ function getRegion(locale: string): string | undefined { function getWeekStart(locale: string): number { // TODO: use Intl.Locale for this once browsers support the weekInfo property // https://github.com/tc39/proposal-intl-locale-info - let region = getRegion(locale); - return region ? weekStartData[region] || 0 : 0; + let weekInfo = cachedWeekInfo.get(locale); + if (!weekInfo) { + if (Intl.Locale) { + // @ts-ignore + let localeInst = new Intl.Locale(locale); + if ('getWeekInfo' in localeInst) { + // @ts-expect-error + weekInfo = localeInst.getWeekInfo(); + if (weekInfo) { + cachedWeekInfo.set(locale, weekInfo); + return weekInfo.firstDay; + } + } + } + let region = getRegion(locale); + if (locale.includes('-fw-')) { + let day = locale.split('-fw-')[1]; + if (day === 'mon') { + weekInfo = {firstDay: 1}; + } else if (day === 'tue') { + weekInfo = {firstDay: 2}; + } else if (day === 'wed') { + weekInfo = {firstDay: 3}; + } else if (day === 'thu') { + weekInfo = {firstDay: 4}; + } else if (day === 'fri') { + weekInfo = {firstDay: 5}; + } else if (day === 'sat') { + weekInfo = {firstDay: 6}; + } else { + weekInfo = {firstDay: 0}; + } + } else if (locale.includes('u-ca-iso8601')) { + weekInfo = {firstDay: 1}; + } else { + weekInfo = {firstDay: region ? weekStartData[region] || 0 : 0}; + } + cachedWeekInfo.set(locale, weekInfo); + } + + return weekInfo.firstDay; } /** Returns the number of weeks in the given month and locale. */ diff --git a/packages/@internationalized/date/tests/queries.test.js b/packages/@internationalized/date/tests/queries.test.js index 5d6c75c0290..8ae3f8c3a54 100644 --- a/packages/@internationalized/date/tests/queries.test.js +++ b/packages/@internationalized/date/tests/queries.test.js @@ -273,6 +273,15 @@ describe('queries', function () { expect(startOfWeek(new CalendarDate(2021, 8, 4), 'fr-FR', 'sun')).toEqual(new CalendarDate(2021, 8, 1)); expect(startOfWeek(new CalendarDate(2021, 8, 4), 'en-US', 'thu')).toEqual(new CalendarDate(2021, 7, 29)); }); + + it('should return the start of the week in en-US-u-ca-iso8601', function () { + // start of week is monday + expect(startOfWeek(new CalendarDate(2021, 8, 4), 'en-US-u-ca-iso8601')).toEqual(new CalendarDate(2021, 8, 2)); + expect(startOfWeek(new CalendarDate(2021, 8, 4), 'fr-FR-u-ca-iso8601')).toEqual(new CalendarDate(2021, 8, 2)); + + // override first day of week + expect(startOfWeek(new CalendarDate(2021, 8, 4), 'en-US-u-ca-iso8601-fw-tue')).toEqual(new CalendarDate(2021, 8, 3)); + }); }); describe('endOfWeek', function () { diff --git a/packages/react-aria-components/stories/Calendar.stories.tsx b/packages/react-aria-components/stories/Calendar.stories.tsx index f97dd0e967c..0703be28137 100644 --- a/packages/react-aria-components/stories/Calendar.stories.tsx +++ b/packages/react-aria-components/stories/Calendar.stories.tsx @@ -10,7 +10,18 @@ * governing permissions and limitations under the License. */ -import {Button, Calendar, CalendarCell, CalendarGrid, CalendarStateContext, Heading, RangeCalendar} from 'react-aria-components'; +import { + Button, + Calendar, + CalendarCell, + CalendarGrid, + CalendarProps, + CalendarStateContext, + DateValue, + Heading, + I18nProvider, + RangeCalendar +} from 'react-aria-components'; import {CalendarDate, parseDate} from '@internationalized/date'; import {Meta, StoryObj} from '@storybook/react'; import React, {useContext} from 'react'; @@ -120,6 +131,41 @@ export const CalendarMultiMonth: CalendarStory = { } }; + +interface CalendarFirstDayOfWeekExampleProps extends CalendarProps { + locale: string +} + +export const CalendarFirstDayOfWeekExample: StoryObj = { + render: function Example(args) { + return ( +
+ + +
+ + + +
+ + {date => ({display: isOutsideMonth ? 'none' : '', textAlign: 'center', cursor: 'default', background: isSelected ? 'blue' : ''})} />} + +
+
+
+ ); + }, + args: { + locale: 'en-US-u-ca-iso8601-fw-tue' + }, + argTypes: { + locale: { + control: 'select', + options: ['en-US-u-ca-iso8601-fw-tue', 'en-US-u-ca-iso8601', 'en-US', 'fr-FR-u-ca-iso8601-fw-tue', 'fr-FR-u-ca-iso8601', 'fr-FR'] + } + } +}; + export const RangeCalendarExample: RangeCalendarStory = { render: () => (