From 9e104e5252ce9a2516febfc71d918f1b0c64e466 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Sun, 8 Oct 2023 22:15:21 +0200 Subject: [PATCH 1/5] remove moment from datepicker --- src/CONST.ts | 1 - .../DatePicker/datepickerPropTypes.js | 4 +- src/components/DatePicker/index.android.js | 9 ++--- src/components/DatePicker/index.ios.js | 9 ++--- src/components/DatePicker/index.js | 12 +++--- .../NewDatePicker/CalendarPicker/index.js | 40 +++++++++---------- src/components/NewDatePicker/index.js | 10 ++--- src/libs/DateUtils.ts | 38 ++++++++++++++++++ tests/unit/CalendarPickerTest.js | 9 +---- 9 files changed, 80 insertions(+), 52 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d3841189fce2..3082e7470c2c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -130,7 +130,6 @@ 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 8bd5d890c42c..f896023d386b 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 `moment` can parse. + * The datepicker supports any value that `new Date()` 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 `moment` can parse. + * The datepicker supports any defaultValue that `new Date()` 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 5bdda580d357..71dd693af405 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 moment from 'moment'; +import {format} from 'date-fns'; import _ from 'underscore'; import TextInput from '../TextInput'; import CONST from '../../CONST'; @@ -28,8 +28,7 @@ class DatePicker extends React.Component { this.setState({isPickerVisible: false}); if (event.type === 'set') { - const asMoment = moment(selectedDate, true); - this.props.onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + this.props.onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING)); } } @@ -39,7 +38,7 @@ class DatePicker extends React.Component { } render() { - const dateAsText = this.props.value || this.props.defaultValue ? moment(this.props.value || this.props.defaultValue).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; + const dateAsText = this.props.value || this.props.defaultValue ? format(new Date(this.props.value || this.props.defaultValue), CONST.DATE.FNS_FORMAT_STRING) : ''; return ( <> @@ -73,7 +72,7 @@ class DatePicker extends React.Component { /> {this.state.isPickerVisible && ( { // Adds nice native datepicker on web/desktop. Not possible to set this through props inputRef.current.setAttribute('type', 'date'); - 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.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.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 asMoment = moment(text, true); - if (asMoment.isValid()) { - onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING)); + const date = new Date(text); + if (isValid(date)) { + onInputChange(format(date, CONST.DATE.FNS_FORMAT_STRING)); } }; diff --git a/src/components/NewDatePicker/CalendarPicker/index.js b/src/components/NewDatePicker/CalendarPicker/index.js index 1e1ef3c3fad3..67b3ef3aa91a 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 moment from 'moment'; +import {setYear, format, getYear, subMonths, addMonths, startOfDay, endOfMonth, setDate, isSameDay} from 'date-fns'; import PropTypes from 'prop-types'; import Str from 'expensify-common/lib/str'; import Text from '../../Text'; @@ -11,6 +11,7 @@ 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'; @@ -34,8 +35,8 @@ const propTypes = { const defaultProps = { value: new Date(), - minDate: moment().year(CONST.CALENDAR_PICKER.MIN_YEAR).toDate(), - maxDate: moment().year(CONST.CALENDAR_PICKER.MAX_YEAR).toDate(), + minDate: setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), + maxDate: setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), onSelected: () => {}, }; @@ -46,16 +47,15 @@ class CalendarPicker extends React.PureComponent { if (props.minDate >= props.maxDate) { throw new Error('Minimum date cannot be greater than the maximum date.'); } - - let currentDateView = moment(props.value, CONST.DATE.MOMENT_FORMAT_STRING).toDate(); + let currentDateView = new Date(props.value); if (props.maxDate < currentDateView) { currentDateView = props.maxDate; } else if (props.minDate > currentDateView) { currentDateView = props.minDate; } - const minYear = moment(this.props.minDate).year(); - const maxYear = moment(this.props.maxDate).year(); + const minYear = getYear(new Date(this.props.minDate)); + const maxYear = getYear(new Date(this.props.maxDate)); this.state = { currentDateView, @@ -79,7 +79,7 @@ class CalendarPicker extends React.PureComponent { onYearSelected(year) { this.setState((prev) => { - const newCurrentDateView = moment(prev.currentDateView).set('year', year).toDate(); + const newCurrentDateView = setYear(new Date(prev.currentDateView), year); return { currentDateView: newCurrentDateView, @@ -99,9 +99,9 @@ class CalendarPicker extends React.PureComponent { onDayPressed(day) { this.setState( (prev) => ({ - currentDateView: moment(prev.currentDateView).set('date', day).toDate(), + currentDateView: setDate(new Date(prev.currentDateView), day), }), - () => this.props.onSelected(moment(this.state.currentDateView).format('YYYY-MM-DD')), + () => this.props.onSelected(format(new Date(this.state.currentDateView), CONST.DATE.FNS_FORMAT_STRING)), ); } @@ -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: moment(prev.currentDateView).subtract(1, 'months').toDate()})); + this.setState((prev) => ({currentDateView: subMonths(new Date(prev.currentDateView), 1)})); } /** * Handles the user pressing the next month arrow of the calendar picker. */ moveToNextMonth() { - this.setState((prev) => ({currentDateView: moment(prev.currentDateView).add(1, 'months').toDate()})); + this.setState((prev) => ({currentDateView: addMonths(new Date(prev.currentDateView), 1)})); } render() { - const monthNames = _.map(moment.localeData(this.props.preferredLocale).months(), Str.recapitalize); - const daysOfWeek = _.map(moment.localeData(this.props.preferredLocale).weekdays(), (day) => day.toUpperCase()); + const monthNames = _.map(DateUtils.getMonthNames(this.props.preferredLocale), Str.recapitalize); + const daysOfWeek = _.map(DateUtils.getDaysOfWeek(this.props.preferredLocale), (day) => day.toUpperCase()); const currentMonthView = this.state.currentDateView.getMonth(); const currentYearView = this.state.currentDateView.getFullYear(); const calendarDaysMatrix = generateMonthMatrix(currentYearView, currentMonthView); - const hasAvailableDatesNextMonth = moment(this.props.maxDate).endOf('month').startOf('day') > moment(this.state.currentDateView).add(1, 'months'); - const hasAvailableDatesPrevMonth = moment(this.props.minDate).startOf('day') < moment(this.state.currentDateView).subtract(1, 'months').endOf('month'); + 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)); return ( @@ -201,11 +201,11 @@ class CalendarPicker extends React.PureComponent { style={styles.flexRow} > {_.map(week, (day, index) => { - 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 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 isDisabled = !day || isBeforeMinDate || isAfterMaxDate; - const isSelected = moment(this.props.value).isSame(moment([currentYearView, currentMonthView, day]), 'day'); + const isSelected = isSameDay(new Date(this.props.value), new Date(currentYearView, currentMonthView, day)); return ( { return timezone; } +/** + * @param {String} preferredLocale + * @returns {Array} + */ +function getMonthNames(preferredLocale) { + 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)); +} + +/** + * @param {String} preferredLocale + * @returns {Object} + */ +function getDaysOfWeek(preferredLocale) { + 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(); @@ -357,6 +393,8 @@ const DateUtils = { isToday, isTomorrow, isYesterday, + getMonthNames, + getDaysOfWeek, }; export default DateUtils; diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index 512a86a25e19..e5a12cc72ed3 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -4,14 +4,7 @@ import DateUtils from '../../src/libs/DateUtils'; import CalendarPicker from '../../src/components/NewDatePicker/CalendarPicker'; import CONST from '../../src/CONST'; -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)); +const monthNames = DateUtils.getMonthNames(CONST.LOCALES.EN); jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({navigate: jest.fn()}), From d695eea9584026dec217ee809beccfdb16273382 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Sun, 8 Oct 2023 22:27:41 +0200 Subject: [PATCH 2/5] typescript adjustment --- src/libs/DateUtils.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index d7935770948b..76cf8f15e522 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -258,10 +258,9 @@ function getCurrentTimezone(): Required { } /** - * @param {String} preferredLocale - * @returns {Array} + * @returns [January, Fabruary, March, April, May, June, July, August, ...] */ -function getMonthNames(preferredLocale) { +function getMonthNames(preferredLocale: string): string[] { if (preferredLocale) { setLocale(preferredLocale); } @@ -276,10 +275,9 @@ function getMonthNames(preferredLocale) { } /** - * @param {String} preferredLocale - * @returns {Object} + * @returns [Monday, Thuesday, Wednesday, ...] */ -function getDaysOfWeek(preferredLocale) { +function getDaysOfWeek(preferredLocale: string): string[] { if (preferredLocale) { setLocale(preferredLocale); } From 70e3e89e827302496a56851617576c31ef076972 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Sun, 8 Oct 2023 22:33:02 +0200 Subject: [PATCH 3/5] lint fix --- tests/unit/CalendarPickerTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index e5a12cc72ed3..235dff45f631 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -1,5 +1,5 @@ import {render, fireEvent, within} from '@testing-library/react-native'; -import {format, eachMonthOfInterval, subYears, addYears} from 'date-fns'; +import {subYears, addYears} from 'date-fns'; import DateUtils from '../../src/libs/DateUtils'; import CalendarPicker from '../../src/components/NewDatePicker/CalendarPicker'; import CONST from '../../src/CONST'; From bfd129dfdc0eb98234ace50d4bcf568b278d1ec1 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 9 Oct 2023 14:49:06 +0200 Subject: [PATCH 4/5] changes --- src/components/DatePicker/index.android.js | 5 +++-- src/components/DatePicker/index.ios.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index 71dd693af405..24faf2b19745 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -38,7 +38,8 @@ class DatePicker extends React.Component { } render() { - const dateAsText = this.props.value || this.props.defaultValue ? format(new Date(this.props.value || this.props.defaultValue), CONST.DATE.FNS_FORMAT_STRING) : ''; + const date = this.props.value || this.props.defaultValue; + const dateAsText = date ? format(new Date(date), CONST.DATE.FNS_FORMAT_STRING) : ''; return ( <> @@ -72,7 +73,7 @@ class DatePicker extends React.Component { /> {this.state.isPickerVisible && ( Date: Tue, 17 Oct 2023 14:24:35 +0200 Subject: [PATCH 5/5] lint fix --- src/components/DatePicker/index.ios.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 7a02f1ef3f62..6af5e26c4bbc 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -2,7 +2,6 @@ import React, {useState, useRef, useCallback, useEffect} from 'react'; import {Button, View, Keyboard} from 'react-native'; import RNDatePicker from '@react-native-community/datetimepicker'; import {format} from 'date-fns'; -import _ from 'underscore'; import isFunction from 'lodash/isFunction'; import TextInput from '../TextInput'; import Popover from '../Popover'; @@ -14,9 +13,9 @@ import useKeyboardState from '../../hooks/useKeyboardState'; import useLocalize from '../../hooks/useLocalize'; function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLocale, minDate, maxDate, label, disabled, onBlur, placeholder, containerStyles, errorText}) { - const date = value || defaultValue; + const dateValue = value || defaultValue; const [isPickerVisible, setIsPickerVisible] = useState(false); - const [selectedDate, setSelectedDate] = useState(date ? new Date(date) : new Date()); + const [selectedDate, setSelectedDate] = useState(dateValue ? new Date(dateValue) : new Date()); const {isKeyboardShown} = useKeyboardState(); const {translate} = useLocalize(); const initialValue = useRef(null); @@ -78,7 +77,7 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca setSelectedDate(date); }; - const dateAsText = date ? format(new Date(date), CONST.DATE.FNS_FORMAT_STRING) : ''; + const dateAsText = dateValue ? format(new Date(dateValue), CONST.DATE.FNS_FORMAT_STRING) : ''; return ( <>