From 870931972c1c25a7c84d802486c38fa44abfab59 Mon Sep 17 00:00:00 2001 From: Francois Laithier Date: Wed, 18 Oct 2023 14:08:14 -0700 Subject: [PATCH] Merge pull request #29889 from Expensify/revert-29062-feat-19810-Replace-datepicker (cherry picked from commit e8962f2e6bf8d2dd5f99786b4530c9781587519d) --- src/CONST.ts | 1 + .../DatePicker/datepickerPropTypes.js | 4 +- src/components/DatePicker/index.android.js | 10 ++--- src/components/DatePicker/index.ios.js | 10 ++--- src/components/DatePicker/index.js | 12 +++--- .../NewDatePicker/CalendarPicker/index.js | 40 +++++++++---------- src/components/NewDatePicker/index.js | 10 ++--- src/libs/DateUtils.ts | 36 ----------------- tests/unit/CalendarPickerTest.js | 11 ++++- 9 files changed, 53 insertions(+), 81 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e2f3fea08215..3c49925e6075 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -131,6 +131,7 @@ const CONST = { DESKTOP: `${ACTIVE_EXPENSIFY_URL}NewExpensify.dmg`, }, DATE: { + MOMENT_FORMAT_STRING: 'YYYY-MM-DD', SQL_DATE_TIME: 'YYYY-MM-DD HH:mm:ss', FNS_FORMAT_STRING: 'yyyy-MM-dd', LOCAL_TIME_FORMAT: 'h:mm a', diff --git a/src/components/DatePicker/datepickerPropTypes.js b/src/components/DatePicker/datepickerPropTypes.js index f896023d386b..8bd5d890c42c 100644 --- a/src/components/DatePicker/datepickerPropTypes.js +++ b/src/components/DatePicker/datepickerPropTypes.js @@ -6,13 +6,13 @@ const propTypes = { ...fieldPropTypes, /** - * The datepicker supports any value that `new Date()` can parse. + * The datepicker supports any value that `moment` can parse. * `onInputChange` would always be called with a Date (or null) */ value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]), /** - * The datepicker supports any defaultValue that `new Date()` can parse. + * The datepicker supports any defaultValue that `moment` can parse. * `onInputChange` would always be called with a Date (or null) */ defaultValue: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]), diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index 24faf2b19745..5bdda580d357 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -1,7 +1,7 @@ import React from 'react'; import {Keyboard} from 'react-native'; import RNDatePicker from '@react-native-community/datetimepicker'; -import {format} from 'date-fns'; +import moment from 'moment'; import _ from 'underscore'; import TextInput from '../TextInput'; import CONST from '../../CONST'; @@ -28,7 +28,8 @@ class DatePicker extends React.Component { this.setState({isPickerVisible: false}); if (event.type === 'set') { - this.props.onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING)); + const asMoment = moment(selectedDate, true); + this.props.onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); } } @@ -38,8 +39,7 @@ class DatePicker extends React.Component { } render() { - const date = this.props.value || this.props.defaultValue; - const dateAsText = date ? format(new Date(date), CONST.DATE.FNS_FORMAT_STRING) : ''; + const dateAsText = this.props.value || this.props.defaultValue ? moment(this.props.value || this.props.defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; return ( <> @@ -73,7 +73,7 @@ class DatePicker extends React.Component { /> {this.state.isPickerVisible && ( { setIsPickerVisible(false); - onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING)); + const asMoment = moment(selectedDate, true); + onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); }; /** @@ -77,7 +77,7 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca setSelectedDate(date); }; - const dateAsText = dateValue ? format(new Date(dateValue), CONST.DATE.FNS_FORMAT_STRING) : ''; + const dateAsText = value || defaultValue ? moment(value || defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; return ( <> diff --git a/src/components/DatePicker/index.js b/src/components/DatePicker/index.js index e0672f847295..d14886fd1c59 100644 --- a/src/components/DatePicker/index.js +++ b/src/components/DatePicker/index.js @@ -1,5 +1,5 @@ import React, {useEffect, useRef} from 'react'; -import {format, isValid} from 'date-fns'; +import moment from 'moment'; import _ from 'underscore'; import TextInput from '../TextInput'; import CONST from '../../CONST'; @@ -13,8 +13,8 @@ function DatePicker({maxDate, minDate, onInputChange, innerRef, label, value, pl useEffect(() => { // Adds nice native datepicker on web/desktop. Not possible to set this through props inputRef.current.setAttribute('type', 'date'); - inputRef.current.setAttribute('max', format(new Date(maxDate), CONST.DATE.FNS_FORMAT_STRING)); - inputRef.current.setAttribute('min', format(new Date(minDate), CONST.DATE.FNS_FORMAT_STRING)); + inputRef.current.setAttribute('max', moment(maxDate).format(CONST.DATE.MOMENT_FORMAT_STRING)); + inputRef.current.setAttribute('min', moment(minDate).format(CONST.DATE.MOMENT_FORMAT_STRING)); inputRef.current.classList.add('expensify-datepicker'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -29,9 +29,9 @@ function DatePicker({maxDate, minDate, onInputChange, innerRef, label, value, pl return; } - const date = new Date(text); - if (isValid(date)) { - onInputChange(format(date, CONST.DATE.FNS_FORMAT_STRING)); + const asMoment = moment(text, true); + if (asMoment.isValid()) { + onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); } }; diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js index 67b3ef3aa91a..d03c36997845 100644 --- a/src/components/NewDatePicker/CalendarPicker/index.js +++ b/src/components/NewDatePicker/CalendarPicker/index.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import React from 'react'; import {View} from 'react-native'; -import {setYear, format, getYear, subMonths, addMonths, startOfDay, endOfMonth, setDate, isSameDay} from 'date-fns'; +import moment from 'moment'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import Text from '../../Text'; @@ -11,7 +11,6 @@ import styles from '../../../styles/styles'; import generateMonthMatrix from './generateMonthMatrix'; import withLocalize, {withLocalizePropTypes} from '../../withLocalize'; import CONST from '../../../CONST'; -import DateUtils from '../../../libs/DateUtils'; import getButtonState from '../../../libs/getButtonState'; import * as StyleUtils from '../../../styles/StyleUtils'; import PressableWithFeedback from '../../Pressable/PressableWithFeedback'; @@ -35,8 +34,8 @@ const propTypes = { const defaultProps = { value: new Date(), - minDate: setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), - maxDate: setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), + minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), + maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), onSelected: () => {}, }; @@ -47,15 +46,16 @@ class CalendarPicker extends React.PureComponent { if (props.minDate >= props.maxDate) { throw new Error('Minimum date cannot be greater than the maximum date.'); } - let currentDateView = new Date(props.value); + + let currentDateView = moment(props.value, CONST.DATE.MOMENT_FORMAT_STRING).toDate(); if (props.maxDate < currentDateView) { currentDateView = props.maxDate; } else if (props.minDate > currentDateView) { currentDateView = props.minDate; } - const minYear = getYear(new Date(this.props.minDate)); - const maxYear = getYear(new Date(this.props.maxDate)); + const minYear = moment(this.props.minDate).year(); + const maxYear = moment(this.props.maxDate).year(); this.state = { currentDateView, @@ -79,7 +79,7 @@ class CalendarPicker extends React.PureComponent { onYearSelected(year) { this.setState((prev) => { - const newCurrentDateView = setYear(new Date(prev.currentDateView), year); + const newCurrentDateView = moment(prev.currentDateView).set('year', year).toDate(); return { currentDateView: newCurrentDateView, @@ -99,9 +99,9 @@ class CalendarPicker extends React.PureComponent { onDayPressed(day) { this.setState( (prev) => ({ - currentDateView: setDate(new Date(prev.currentDateView), day), + currentDateView: moment(prev.currentDateView).set('date', day).toDate(), }), - () => this.props.onSelected(format(new Date(this.state.currentDateView), CONST.DATE.FNS_FORMAT_STRING)), + () => this.props.onSelected(moment(this.state.currentDateView).format('YYYY-MM-DD')), ); } @@ -109,24 +109,24 @@ class CalendarPicker extends React.PureComponent { * Handles the user pressing the previous month arrow of the calendar picker. */ moveToPrevMonth() { - this.setState((prev) => ({currentDateView: subMonths(new Date(prev.currentDateView), 1)})); + this.setState((prev) => ({currentDateView: moment(prev.currentDateView).subtract(1, 'months').toDate()})); } /** * Handles the user pressing the next month arrow of the calendar picker. */ moveToNextMonth() { - this.setState((prev) => ({currentDateView: addMonths(new Date(prev.currentDateView), 1)})); + this.setState((prev) => ({currentDateView: moment(prev.currentDateView).add(1, 'months').toDate()})); } render() { - const monthNames = _.map(DateUtils.getMonthNames(this.props.preferredLocale), Str.recapitalize); - const daysOfWeek = _.map(DateUtils.getDaysOfWeek(this.props.preferredLocale), (day) => day.toUpperCase()); + const monthNames = _.map(moment.localeData(this.props.preferredLocale).months(), Str.recapitalize); + const daysOfWeek = _.map(moment.localeData(this.props.preferredLocale).weekdays(), (day) => day.toUpperCase()); const currentMonthView = this.state.currentDateView.getMonth(); const currentYearView = this.state.currentDateView.getFullYear(); const calendarDaysMatrix = generateMonthMatrix(currentYearView, currentMonthView); - const hasAvailableDatesNextMonth = startOfDay(endOfMonth(new Date(this.props.maxDate))) > addMonths(new Date(this.state.currentDateView), 1); - const hasAvailableDatesPrevMonth = startOfDay(new Date(this.props.minDate)) < endOfMonth(subMonths(new Date(this.state.currentDateView), 1)); + const hasAvailableDatesNextMonth = moment(this.props.maxDate).endOf('month').endOf('day') >= moment(this.state.currentDateView).add(1, 'months'); + const hasAvailableDatesPrevMonth = moment(this.props.minDate).startOf('month').startOf('day') <= moment(this.state.currentDateView).subtract(1, 'months'); return ( @@ -201,11 +201,11 @@ class CalendarPicker extends React.PureComponent { style={styles.flexRow} > {_.map(week, (day, index) => { - const currentDate = new Date(currentYearView, currentMonthView, day); - const isBeforeMinDate = currentDate < startOfDay(new Date(this.props.minDate)); - const isAfterMaxDate = currentDate > startOfDay(new Date(this.props.maxDate)); + const currentDate = moment([currentYearView, currentMonthView, day]); + const isBeforeMinDate = currentDate < moment(this.props.minDate).startOf('day'); + const isAfterMaxDate = currentDate > moment(this.props.maxDate).startOf('day'); const isDisabled = !day || isBeforeMinDate || isAfterMaxDate; - const isSelected = isSameDay(new Date(this.props.value), new Date(currentYearView, currentMonthView, day)); + const isSelected = moment(this.props.value).isSame(moment([currentYearView, currentMonthView, day]), 'day'); return ( { return timezone; } -/** - * @returns [January, Fabruary, March, April, May, June, July, August, ...] - */ -function getMonthNames(preferredLocale: string): string[] { - if (preferredLocale) { - setLocale(preferredLocale); - } - const fullYear = new Date().getFullYear(); - const monthsArray = eachMonthOfInterval({ - start: new Date(fullYear, 0, 1), // January 1st of the current year - end: new Date(fullYear, 11, 31), // December 31st of the current year - }); - - // eslint-disable-next-line rulesdir/prefer-underscore-method - return monthsArray.map((monthDate) => format(monthDate, CONST.DATE.MONTH_FORMAT)); -} - -/** - * @returns [Monday, Thuesday, Wednesday, ...] - */ -function getDaysOfWeek(preferredLocale: string): 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 daysOfWeek = eachDayOfInterval({start: startOfCurrentWeek, end: endOfCurrentWeek}); - - // eslint-disable-next-line rulesdir/prefer-underscore-method - return daysOfWeek.map((date) => format(date, 'eeee')); -} - // Used to throttle updates to the timezone when necessary let lastUpdatedTimezoneTime = new Date(); @@ -391,8 +357,6 @@ const DateUtils = { isToday, isTomorrow, isYesterday, - getMonthNames, - getDaysOfWeek, }; export default DateUtils; diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index 235dff45f631..512a86a25e19 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -1,10 +1,17 @@ import {render, fireEvent, within} from '@testing-library/react-native'; -import {subYears, addYears} from 'date-fns'; +import {format, eachMonthOfInterval, subYears, addYears} from 'date-fns'; import DateUtils from '../../src/libs/DateUtils'; import CalendarPicker from '../../src/components/NewDatePicker/CalendarPicker'; import CONST from '../../src/CONST'; -const monthNames = DateUtils.getMonthNames(CONST.LOCALES.EN); +DateUtils.setLocale(CONST.LOCALES.EN); +const fullYear = new Date().getFullYear(); +const monthsArray = eachMonthOfInterval({ + start: new Date(fullYear, 0, 1), // January 1st of the current year + end: new Date(fullYear, 11, 31), // December 31st of the current year +}); +// eslint-disable-next-line rulesdir/prefer-underscore-method +const monthNames = monthsArray.map((monthDate) => format(monthDate, CONST.DATE.MONTH_FORMAT)); jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({navigate: jest.fn()}),