Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Feat: Remove moment from datepicker" #29889

Merged
merged 1 commit into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions src/components/DatePicker/datepickerPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down
10 changes: 5 additions & 5 deletions src/components/DatePicker/index.android.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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));
}
}

Expand All @@ -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 (
<>
Expand Down Expand Up @@ -73,7 +73,7 @@ class DatePicker extends React.Component {
/>
{this.state.isPickerVisible && (
<RNDatePicker
value={date ? new Date(date) : new Date()}
value={this.props.value || this.props.defaultValue ? moment(this.props.value || this.props.defaultValue).toDate() : new Date()}
mode="date"
onChange={this.setDate}
maximumDate={this.props.maxDate}
Expand Down
10 changes: 5 additions & 5 deletions src/components/DatePicker/index.ios.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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 moment from 'moment';
import isFunction from 'lodash/isFunction';
import TextInput from '../TextInput';
import Popover from '../Popover';
Expand All @@ -13,9 +13,8 @@ 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 dateValue = value || defaultValue;
const [isPickerVisible, setIsPickerVisible] = useState(false);
const [selectedDate, setSelectedDate] = useState(dateValue ? new Date(dateValue) : new Date());
const [selectedDate, setSelectedDate] = useState(moment(value || defaultValue).toDate());
const {isKeyboardShown} = useKeyboardState();
const {translate} = useLocalize();
const initialValue = useRef(null);
Expand Down Expand Up @@ -66,7 +65,8 @@ function DatePicker({value, defaultValue, innerRef, onInputChange, preferredLoca
*/
const selectDate = () => {
setIsPickerVisible(false);
onInputChange(format(selectedDate, CONST.DATE.FNS_FORMAT_STRING));
const asMoment = moment(selectedDate, true);
onInputChange(asMoment.format(CONST.DATE.MOMENT_FORMAT_STRING));
};

/**
Expand All @@ -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 (
<>
Expand Down
12 changes: 6 additions & 6 deletions src/components/DatePicker/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
}, []);
Expand All @@ -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));
}
};

Expand Down
40 changes: 20 additions & 20 deletions src/components/NewDatePicker/CalendarPicker/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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: () => {},
};

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -99,34 +99,34 @@ 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')),
);
}

/**
* 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 (
<View>
Expand Down Expand Up @@ -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 (
<PressableWithoutFeedback
Expand Down
10 changes: 5 additions & 5 deletions src/components/NewDatePicker/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import {setYear} from 'date-fns';
import moment from 'moment';
import PropTypes from 'prop-types';
import _ from 'lodash';
import TextInput from '../TextInput';
Expand All @@ -14,13 +14,13 @@ import InputWrapper from '../Form/InputWrapper';

const propTypes = {
/**
* 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.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.string,
Expand All @@ -39,8 +39,8 @@ const propTypes = {

const datePickerDefaultProps = {
...defaultBaseTextInputPropTypes,
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(),
value: undefined,
};

Expand Down
36 changes: 0 additions & 36 deletions src/libs/DateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import {
isSameDay,
isAfter,
isSameYear,
eachMonthOfInterval,
eachDayOfInterval,
} from 'date-fns';

import Onyx from 'react-native-onyx';
Expand Down Expand Up @@ -257,38 +255,6 @@ function getCurrentTimezone(): Required<Timezone> {
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();

Expand Down Expand Up @@ -391,8 +357,6 @@ const DateUtils = {
isToday,
isTomorrow,
isYesterday,
getMonthNames,
getDaysOfWeek,
};

export default DateUtils;
11 changes: 9 additions & 2 deletions tests/unit/CalendarPickerTest.js
Original file line number Diff line number Diff line change
@@ -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()}),
Expand Down
Loading