diff --git a/src/CONST.ts b/src/CONST.ts index 0fc684347243..34720adf8a21 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -821,6 +821,7 @@ const CONST = { MAX_PENDING_TIME_MS: 10 * 1000, MAX_REQUEST_RETRIES: 10, }, + WEEK_STARTS_ON: 1, // Monday DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, DEFAULT_CLOSE_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, diff --git a/src/components/DatePicker/CalendarPicker/generateMonthMatrix.js b/src/components/DatePicker/CalendarPicker/generateMonthMatrix.js index a3497654feec..ecf338d36424 100644 --- a/src/components/DatePicker/CalendarPicker/generateMonthMatrix.js +++ b/src/components/DatePicker/CalendarPicker/generateMonthMatrix.js @@ -1,4 +1,5 @@ import {addDays, format, getDay, getDaysInMonth, startOfMonth} from 'date-fns'; +import DateUtils from '@libs/DateUtils'; /** * Generates a matrix representation of a month's calendar given the year and month. @@ -24,6 +25,9 @@ export default function generateMonthMatrix(year, month) { throw new Error('Month cannot be greater than 11'); } + // Get the week day for the end of week + const weekEndsOn = DateUtils.getWeekEndsOn(); + // Get the number of days in the month and the first day of the month const firstDayOfMonth = startOfMonth(new Date(year, month, 1)); const daysInMonth = getDaysInMonth(firstDayOfMonth); @@ -32,18 +36,13 @@ export default function generateMonthMatrix(year, month) { const matrix = []; let currentWeek = []; - // Add null values for days before the first day of the month - for (let i = 0; i < getDay(firstDayOfMonth); i++) { - currentWeek.push(null); - } - // Add calendar days to the matrix for (let i = 1; i <= daysInMonth; i++) { const currentDate = addDays(firstDayOfMonth, i - 1); currentWeek.push(Number(format(currentDate, 'd'))); // Start a new row when the current week is full - if (getDay(currentDate) === 6) { + if (getDay(currentDate) === weekEndsOn) { matrix.push(currentWeek); currentWeek = []; } @@ -56,5 +55,11 @@ export default function generateMonthMatrix(year, month) { } matrix.push(currentWeek); } + + // Add null values for days before the first day of the month + for (let i = matrix[0].length; i < 7; i++) { + matrix[0].unshift(null); + } + return matrix; } diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 17739d63c9c7..4bd717eba6c0 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -38,6 +38,7 @@ import Log from './Log'; type CustomStatusTypes = (typeof CONST.CUSTOM_STATUS_TYPES)[keyof typeof CONST.CUSTOM_STATUS_TYPES]; type TimePeriod = 'AM' | 'PM'; type Locale = ValueOf; +type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6; let currentUserAccountID: number | undefined; Onyx.connect({ @@ -69,6 +70,22 @@ Onyx.connect({ }, }); +/** + * Get the day of the week that the week starts on + */ +function getWeekStartsOn(): WeekDay { + return CONST.WEEK_STARTS_ON; +} + +/** + * Get the day of the week that the week ends on + */ +function getWeekEndsOn(): WeekDay { + const weekStartsOn = getWeekStartsOn(); + + return weekStartsOn === 0 ? 6 : ((weekStartsOn - 1) as WeekDay); +} + /** * Gets the locale string and setting default locale for date-fns */ @@ -163,9 +180,10 @@ function datetimeToCalendarTime(locale: Locale, datetime: string, includeTimeZon let tomorrowAt = Localize.translate(locale, 'common.tomorrowAt'); let yesterdayAt = Localize.translate(locale, 'common.yesterdayAt'); const at = Localize.translate(locale, 'common.conjunctionAt'); + const weekStartsOn = getWeekStartsOn(); - const startOfCurrentWeek = startOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week - const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week + const startOfCurrentWeek = startOfWeek(new Date(), {weekStartsOn}); + const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn}); if (isLowercase) { todayAt = todayAt.toLowerCase(); @@ -302,8 +320,9 @@ function getDaysOfWeek(preferredLocale: Locale): string[] { if (preferredLocale) { setLocale(preferredLocale); } - const startOfCurrentWeek = startOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week - const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn: 1}); // Assuming Monday is the start of the week + const weekStartsOn = getWeekStartsOn(); + const startOfCurrentWeek = startOfWeek(new Date(), {weekStartsOn}); + const endOfCurrentWeek = endOfWeek(new Date(), {weekStartsOn}); const daysOfWeek = eachDayOfInterval({start: startOfCurrentWeek, end: endOfCurrentWeek}); // eslint-disable-next-line rulesdir/prefer-underscore-method @@ -717,6 +736,8 @@ const DateUtils = { getMonthNames, getDaysOfWeek, formatWithUTCTimeZone, + getWeekStartsOn, + getWeekEndsOn, isTimeAtLeastOneMinuteInFuture, }; diff --git a/tests/unit/generateMonthMatrixTest.js b/tests/unit/generateMonthMatrixTest.js index e65543b82f17..67dd65e6b1fd 100644 --- a/tests/unit/generateMonthMatrixTest.js +++ b/tests/unit/generateMonthMatrixTest.js @@ -3,39 +3,72 @@ import generateMonthMatrix from '../../src/components/DatePicker/CalendarPicker/ describe('generateMonthMatrix', () => { it('returns the correct matrix for January 2022', () => { const expected = [ - [null, null, null, null, null, null, 1], - [2, 3, 4, 5, 6, 7, 8], - [9, 10, 11, 12, 13, 14, 15], - [16, 17, 18, 19, 20, 21, 22], - [23, 24, 25, 26, 27, 28, 29], - [30, 31, null, null, null, null, null], + [null, null, null, null, null, 1, 2], + [3, 4, 5, 6, 7, 8, 9], + [10, 11, 12, 13, 14, 15, 16], + [17, 18, 19, 20, 21, 22, 23], + [24, 25, 26, 27, 28, 29, 30], + [31, null, null, null, null, null, null], ]; expect(generateMonthMatrix(2022, 0)).toEqual(expected); }); it('returns the correct matrix for February 2022', () => { const expected = [ - [null, null, 1, 2, 3, 4, 5], - [6, 7, 8, 9, 10, 11, 12], - [13, 14, 15, 16, 17, 18, 19], - [20, 21, 22, 23, 24, 25, 26], - [27, 28, null, null, null, null, null], + [null, 1, 2, 3, 4, 5, 6], + [7, 8, 9, 10, 11, 12, 13], + [14, 15, 16, 17, 18, 19, 20], + [21, 22, 23, 24, 25, 26, 27], + [28, null, null, null, null, null, null], ]; expect(generateMonthMatrix(2022, 1)).toEqual(expected); }); it('returns the correct matrix for leap year February 2020', () => { const expected = [ - [null, null, null, null, null, null, 1], - [2, 3, 4, 5, 6, 7, 8], - [9, 10, 11, 12, 13, 14, 15], - [16, 17, 18, 19, 20, 21, 22], - [23, 24, 25, 26, 27, 28, 29], + [null, null, null, null, null, 1, 2], + [3, 4, 5, 6, 7, 8, 9], + [10, 11, 12, 13, 14, 15, 16], + [17, 18, 19, 20, 21, 22, 23], + [24, 25, 26, 27, 28, 29, null], ]; expect(generateMonthMatrix(2020, 1)).toEqual(expected); }); it('returns the correct matrix for March 2022', () => { + const expected = [ + [null, 1, 2, 3, 4, 5, 6], + [7, 8, 9, 10, 11, 12, 13], + [14, 15, 16, 17, 18, 19, 20], + [21, 22, 23, 24, 25, 26, 27], + [28, 29, 30, 31, null, null, null], + ]; + expect(generateMonthMatrix(2022, 2)).toEqual(expected); + }); + + it('returns the correct matrix for April 2022', () => { + const expected = [ + [null, null, null, null, 1, 2, 3], + [4, 5, 6, 7, 8, 9, 10], + [11, 12, 13, 14, 15, 16, 17], + [18, 19, 20, 21, 22, 23, 24], + [25, 26, 27, 28, 29, 30, null], + ]; + expect(generateMonthMatrix(2022, 3)).toEqual(expected); + }); + + it('returns the correct matrix for December 2022', () => { + const expected = [ + [null, null, null, 1, 2, 3, 4], + [5, 6, 7, 8, 9, 10, 11], + [12, 13, 14, 15, 16, 17, 18], + [19, 20, 21, 22, 23, 24, 25], + [26, 27, 28, 29, 30, 31, null], + ]; + expect(generateMonthMatrix(2022, 11)).toEqual(expected); + }); + + it('returns the correct matrix for January 2025', () => { const expected = [ [null, null, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11, 12], @@ -43,29 +76,41 @@ describe('generateMonthMatrix', () => { [20, 21, 22, 23, 24, 25, 26], [27, 28, 29, 30, 31, null, null], ]; - expect(generateMonthMatrix(2022, 2)).toEqual(expected); + expect(generateMonthMatrix(2025, 0)).toEqual(expected); }); - it('returns the correct matrix for April 2022', () => { + it('returns the correct matrix for February 2025', () => { const expected = [ [null, null, null, null, null, 1, 2], [3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16], [17, 18, 19, 20, 21, 22, 23], - [24, 25, 26, 27, 28, 29, 30], + [24, 25, 26, 27, 28, null, null], ]; - expect(generateMonthMatrix(2022, 3)).toEqual(expected); + expect(generateMonthMatrix(2025, 1)).toEqual(expected); }); - it('returns the correct matrix for December 2022', () => { + it('returns the correct matrix for June 2025', () => { const expected = [ - [null, null, null, null, 1, 2, 3], - [4, 5, 6, 7, 8, 9, 10], - [11, 12, 13, 14, 15, 16, 17], - [18, 19, 20, 21, 22, 23, 24], - [25, 26, 27, 28, 29, 30, 31], + [null, null, null, null, null, null, 1], + [2, 3, 4, 5, 6, 7, 8], + [9, 10, 11, 12, 13, 14, 15], + [16, 17, 18, 19, 20, 21, 22], + [23, 24, 25, 26, 27, 28, 29], + [30, null, null, null, null, null, null], ]; - expect(generateMonthMatrix(2022, 11)).toEqual(expected); + expect(generateMonthMatrix(2025, 5)).toEqual(expected); + }); + + it('returns the correct matrix for December 2025', () => { + const expected = [ + [1, 2, 3, 4, 5, 6, 7], + [8, 9, 10, 11, 12, 13, 14], + [15, 16, 17, 18, 19, 20, 21], + [22, 23, 24, 25, 26, 27, 28], + [29, 30, 31, null, null, null, null], + ]; + expect(generateMonthMatrix(2025, 11)).toEqual(expected); }); it('throws an error if month is less than 0', () => {