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

Migrate to datefns #5065

Merged
merged 16 commits into from Jul 27, 2022
Merged
1 change: 0 additions & 1 deletion .eslintrc
Expand Up @@ -84,7 +84,6 @@
"globals": {
"I18n": true,
"_": true,
"moment": true,
"Sentry": true,
"SurveyDetails": true,
"Features": true,
Expand Down
5 changes: 2 additions & 3 deletions app/assets/javascripts/components/activity/activity_table.jsx
Expand Up @@ -4,8 +4,7 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { flatten, zip } from 'lodash-es';
import moment from 'moment';

import { formatDateWithTime } from '../../utils/date_utils';
import * as UIActions from '../../actions';
import ActivityTableRow from './activity_table_row.jsx';
import Loading from '../common/loading.jsx';
Expand Down Expand Up @@ -37,7 +36,7 @@ const ActivityTable = createReactClass({
_renderActivites() {
return this.props.activity.map((revision) => {
const roundedRevisionScore = Math.round(revision.revision_score) || 'unknown';
const revisionDateTime = moment(revision.datetime).format('YYYY/MM/DD h:mm a');
const revisionDateTime = formatDateWithTime(revision.datetime);
const talkPageLink = `${revision.base_url}/wiki/User_talk:${revision.username}`;
const isOpen = this.props.openKey === `drawer_${revision.key}`;

Expand Down
4 changes: 2 additions & 2 deletions app/assets/javascripts/components/alerts/alert.jsx
@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'react-redux';

import { handleResolveAlert } from '../../actions/alert_actions';
import { formatDateWithTime } from '../../utils/date_utils';

const Alert = ({ alert, adminAlert, resolveAlert }) => {
let resolveCell;
Expand Down Expand Up @@ -34,7 +34,7 @@ const Alert = ({ alert, adminAlert, resolveAlert }) => {

return (
<tr className="alert">
<td className="desktop-only-tc date">{moment(alert.created_at).format('YYYY-MM-DD h:mm A')}</td>
<td className="desktop-only-tc date">{formatDateWithTime(alert.created_at)}</td>
{alertTypeCell}
<td className="desktop-only-tc"><a target="_blank" href={`/courses/${alert.course_slug}`}>{alert.course}</a></td>
<td className="desktop-only-tc"><a target="_blank" href={`/users/${alert.user}`}>{alert.user}</a></td>
Expand Down
10 changes: 5 additions & 5 deletions app/assets/javascripts/components/categories/category.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import CourseUtils from '../../utils/course_utils';
import moment from 'moment';
import { toDate } from '../../utils/date_utils';
import { isSameDay, formatDistanceToNow } from 'date-fns';

const Category = ({ course, category, remove, editable }) => {
let removeButton;
Expand All @@ -21,13 +22,12 @@ const Category = ({ course, category, remove, editable }) => {
} else {
link = `https://${course.home_wiki.language}.${course.home_wiki.project}.org/wiki/${catName}`;
}
const lastUpdate = category.updated_at;
const lastUpdateMoment = moment.utc(lastUpdate);
const lastUpdate = toDate(category.updated_at);
let lastUpdateMessage;
if (lastUpdate) {
lastUpdateMessage = moment(lastUpdate).isSame(category.created_at)
lastUpdateMessage = isSameDay(toDate(category.created_at), lastUpdate)
? '---'
: `${I18n.t('metrics.last_update')}: ${lastUpdateMoment.fromNow()}`;
: `${I18n.t('metrics.last_update')}: ${formatDistanceToNow(lastUpdate, { addSuffix: true })}`;
}

return (
Expand Down
27 changes: 15 additions & 12 deletions app/assets/javascripts/components/common/calendar.jsx
Expand Up @@ -3,10 +3,10 @@ import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import DayPicker from 'react-day-picker';
import { compact } from 'lodash-es';
import moment from 'moment';

import WeekdayPicker from './weekday_picker.jsx';
import CourseDateUtils from '../../utils/course_date_utils.js';
import { toDate } from '../../utils/date_utils.js';
import { format, getDay, isValid } from 'date-fns';

function __in__(needle, haystack) {
return haystack.indexOf(needle) >= 0;
Expand All @@ -26,19 +26,19 @@ const Calendar = createReactClass({
statics: {
getDerivedStateFromProps(props) {
return {
initialMonth: moment(props.course.start, 'YYYY-MM-DD').toDate()
initialMonth: toDate(props.course.start)
};
}
},

getInitialState() {
return ({ initialMonth: moment(this.props.course.start, 'YYYY-MM-DD').toDate() });
return ({ initialMonth: toDate(this.props.course.start) });
},
shouldComponentUpdate(nextProps) {
if (nextProps.course) {
const start = moment(nextProps.course.start, 'YYYY-MM-DD');
const end = moment(nextProps.course.end, 'YYYY-MM-DD');
return start.isValid() && end.isValid();
const start = toDate(nextProps.course.start);
const end = toDate(nextProps.course.end);
return isValid(start) && isValid(end);
}
return true;
},
Expand All @@ -52,7 +52,7 @@ const Calendar = createReactClass({
} else {
exceptions = course.day_exceptions.split(',');
}
const formatted = moment(day).format('YYYYMMDD');
const formatted = format(day, 'yyyyMMdd');
if (__in__(formatted, exceptions)) {
exceptions.splice(exceptions.indexOf(formatted), 1);
} else {
Expand Down Expand Up @@ -99,15 +99,17 @@ const Calendar = createReactClass({
} else if (day < 8) {
return false;
}
const formatted = moment(day).format('YYYYMMDD');
const formatted = format(day, 'yyyyMMdd');
const inrange = this.inrange(day);
let exception = false;
let weekday = false;
if (this.props.course.day_exceptions !== undefined) {
exception = __in__(formatted, this.props.course.day_exceptions.split(','));
}
if (this.props.course.weekdays) {
weekday = this.props.course.weekdays.charAt(moment(day).format('e')) === '1';
// from 0 to 6. 0 is sunday
const weekNumber = getDay(day);
weekday = this.props.course.weekdays.charAt(weekNumber) === '1';
}
return inrange && ((weekday && !exception) || (!weekday && exception));
},
Expand All @@ -118,10 +120,11 @@ const Calendar = createReactClass({
['bordered']: (day) => {
if (day <= 7) { return false; }
if (!this.props.course.day_exceptions || !this.props.course.weekdays) { return false; }
const formatted = moment(day).format('YYYYMMDD');
const formatted = format(day, 'yyyyMMdd');
const inrange = this.inrange(day);
const exception = __in__(formatted, this.props.course.day_exceptions.split(','));
const weekday = this.props.course.weekdays.charAt(moment(day).format('e')) === '1';
const weekNumber = getDay(day);
const weekday = this.props.course.weekdays.charAt(weekNumber) === '1';
return inrange && exception && weekday;
}
};
Expand Down
50 changes: 23 additions & 27 deletions app/assets/javascripts/components/common/date_picker.jsx
Expand Up @@ -4,11 +4,11 @@ import PropTypes from 'prop-types';
import DayPicker from 'react-day-picker';
import OnClickOutside from 'react-onclickoutside';
import { range, includes } from 'lodash-es';
import moment from 'moment';
import { startOfDay, endOfDay, isValid, isAfter, parseISO } from 'date-fns';
import { startOfDay, endOfDay, isValid, isAfter, parseISO, getHours, getMinutes, setHours, setMinutes, formatISO } from 'date-fns';
import InputHOC from '../high_order/input_hoc.jsx';
import Conditional from '../high_order/conditional.jsx';
import CourseDateUtils from '../../utils/course_date_utils.js';
import { formatDateWithoutTime, toDate } from '../../utils/date_utils.js';

const DatePicker = createReactClass({
displayName: 'DatePicker',
Expand Down Expand Up @@ -45,11 +45,11 @@ const DatePicker = createReactClass({

getInitialState() {
if (this.props.value) {
const dateObj = this.moment(this.props.value);
const dateObj = toDate(this.props.value);
return {
value: dateObj.format('YYYY-MM-DD'),
hour: dateObj.hour(),
minute: dateObj.minute(),
value: formatDateWithoutTime(dateObj),
hour: getHours(dateObj),
minute: getMinutes(dateObj),
datePickerVisible: false
};
}
Expand All @@ -68,22 +68,22 @@ const DatePicker = createReactClass({
* @return {null}
*/
onChangeHandler() {
const e = { target: { value: this.getDate().format() } };
const e = { target: { value: formatISO(this.getDate()) } };
this.props.onChange(e);
},

/**
* Get moment object of currently select date, hour and minute
* @return {moment}
* Get date object of currently select date, hour and minute
* @return {Date}
*/
getDate() {
let dateObj = this.moment(this.state.value, 'YYYY-MM-DD');
dateObj = dateObj.hour(this.state.hour);
return dateObj.minute(this.state.minute);
let dateObj = toDate(this.state.value);
dateObj = setHours(dateObj, this.state.hour);
return setMinutes(dateObj, this.state.minute);
},

getFormattedDate() {
return this.getDate().format('YYYY-MM-DD');
return formatDateWithoutTime(this.getDate());
},

/**
Expand All @@ -92,7 +92,7 @@ const DatePicker = createReactClass({
* @return {String} formatted date
*/
getFormattedDateTime() {
return CourseDateUtils.formattedDateTime(this.getDate().toDate(), this.props.showTime);
return CourseDateUtils.formattedDateTime(this.getDate(), this.props.showTime);
},

getTimeDropdownOptions(type) {
Expand All @@ -106,13 +106,13 @@ const DatePicker = createReactClass({
},

handleDatePickerChange(selectedDate) {
const date = this.moment(selectedDate);
const date = toDate(selectedDate);
if (this.isDayDisabled(date)) {
return;
}
this.refs.datefield.focus();
this.setState({
value: date.format('YYYY-MM-DD'),
value: formatDateWithoutTime(date),
datePickerVisible: false
}, this.onChangeHandler);
},
Expand Down Expand Up @@ -183,7 +183,7 @@ const DatePicker = createReactClass({
},

isDaySelected(date) {
const currentDate = this.moment(date).format('YYYY-MM-DD');
const currentDate = formatDateWithoutTime(date);
return currentDate === this.state.value;
},

Expand All @@ -209,11 +209,7 @@ const DatePicker = createReactClass({
*/
isValidDate(value) {
const validationRegex = /^20\d\d-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])/;
return validationRegex.test(value) && moment(value, 'YYYY-MM-DD').isValid();
},

moment(...args) {
return this.props.showTime ? moment(...args) : moment(...args).utc().local();
return validationRegex.test(value) && isValid(toDate(value));
},

showCurrentDate() {
Expand Down Expand Up @@ -259,13 +255,13 @@ const DatePicker = createReactClass({
}

// don't validate YYYY-MM-DD format so we can update the daypicker as they type
const date = moment(this.state.value, 'YYYY-MM-DD');
if (date.isValid()) {
currentMonth = this.moment(date).toDate();
const date = parseISO(this.state.value);
if (isValid(date)) {
currentMonth = date;
} else if (minDate) {
currentMonth = this.moment(minDate).toDate();
currentMonth = minDate;
} else {
currentMonth = this.moment().toDate();
currentMonth = new Date();
}

const modifiers = {
Expand Down
8 changes: 6 additions & 2 deletions app/assets/javascripts/components/course/course_alerts.jsx
@@ -1,11 +1,12 @@
import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';

import CourseUtils from '../../utils/course_utils';
import CourseAlert from './course_alert';
import OptInNotification from '../common/opt_in_notification';
import { isAfter, startOfDay } from 'date-fns';
import { toDate } from '../../utils/date_utils';

const CourseAlerts = createReactClass({
displayName: 'CourseAlerts',
Expand Down Expand Up @@ -116,7 +117,10 @@ const CourseAlerts = createReactClass({
if (course.incomplete_assigned_modules && course.incomplete_assigned_modules.length) {
// `table` key is because it comes back as an openstruct
const module = course.incomplete_assigned_modules[0].table;
const messageKey = moment().isAfter(module.due_date, 'day') ? 'courses.training_overdue' : 'courses.training_due';
const messageKey = isAfter(
startOfDay(new Date()),
startOfDay(toDate(module.due_date))
) ? 'courses.training_overdue' : 'courses.training_due';

alerts.push(<CourseAlert key="upcoming_module" message={I18n.t(messageKey, { title: module.title, date: module.due_date })} buttonLink={module.link} actionClassName="pull-right" actionMessage={I18n.t('courses.training_nav')} />);
}
Expand Down
@@ -1,10 +1,11 @@
import moment from 'moment';
import { getMonth, getYear } from 'date-fns';
import { toDate } from '~/app/assets/javascripts/utils/date_utils';

export const inferDefaultCampaign = (campaigns, course_start) => {
// Try to infer the best suitable campaign from the start date of course.
// Use the month and year to get the suitable term and compare it with the campaign slug.
const month = moment(course_start).month();
const year = moment(course_start).year();
const month = getMonth(toDate(course_start));
const year = getYear(toDate(course_start));

let term;
switch (month) {
Expand Down
@@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import GreetStudentsButton from './greet_students_button.jsx';
import { format, toDate } from 'date-fns';
import { getUTCDateString } from '../../utils/date_utils.js';

// Helper Functions
const DetailsText = ({ flags }) => (
Expand All @@ -12,7 +13,7 @@ const DetailsText = ({ flags }) => (
</strong>&nbsp;on
<br />
<strong>
{moment(flags.last_reviewed.timestamp).format('LLLL')}
{format(toDate(flags.last_reviewed.timestamp), 'PPPP p')}
</strong>
</p>
);
Expand All @@ -36,7 +37,7 @@ export const AdminQuickActions = ({ course, current_user, persistCourse, greetSt
onClick={() => {
course.last_reviewed = {
username: current_user.username,
timestamp: moment.utc().format()
timestamp: getUTCDateString(),
};
persistCourse(course.slug);
}}
Expand Down
@@ -1,14 +1,15 @@
import React from 'react';
import moment from 'moment';
import { nextUpdateExpected, getLastUpdateSummary, getTotaUpdatesMessage, getUpdateLogs } from '../../utils/statistic_update_info_utils';
import ArticleUtils from '../../utils/article_utils';
import { isAfter, format } from 'date-fns';
import { getUTCDate, toDate } from '../../utils/date_utils';

const StatisticsUpdateModal = (props) => {
const course = props.course;
const helpMessage = Features.wikiEd ? I18n.t('metrics.wiki_ed_help') : I18n.t('metrics.outreach_help');
const updatesEndMoment = moment.utc(course.update_until);
const futureUpdatesRemaining = updatesEndMoment.isAfter();
const futureUpdatesMessage = futureUpdatesRemaining ? I18n.t('metrics.future_updates_remaining.true', { date: updatesEndMoment.format('MMMM Do YYYY') }) : I18n.t('metrics.future_updates_remaining.false');
const updatesEndMoment = toDate(course.update_until);
const futureUpdatesRemaining = isAfter(updatesEndMoment, new Date());
const futureUpdatesMessage = futureUpdatesRemaining ? I18n.t('metrics.future_updates_remaining.true', { date: format(getUTCDate(updatesEndMoment), 'MMMM do yyyy') }) : I18n.t('metrics.future_updates_remaining.false');
const additionalUpdateMessage = course.needs_update ? I18n.t('metrics.non_updating_course_update') : '';

const lastUpdateSummary = getLastUpdateSummary(course);
Expand Down
5 changes: 3 additions & 2 deletions app/assets/javascripts/components/overview/this_week.jsx
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import moment from 'moment';
import Week from '../timeline/week.jsx';
import CourseDateUtils from '../../utils/course_date_utils.js';
import { differenceInDays } from 'date-fns';
import { toDate } from '../../utils/date_utils.js';

const emptyWeeksAtBeginning = function (weekMeetings) {
let count = 0;
Expand Down Expand Up @@ -42,7 +43,7 @@ const ThisWeek = ({ course, weeks, current_user }) => {
weekMeetings = CourseDateUtils.weekMeetings(course, course.day_exceptions);
const emptyWeeksAtStart = emptyWeeksAtBeginning(weekMeetings);
const daysUntilBeginning = emptyWeeksAtStart * 7;
const isFirstWeek = moment().diff(course.timeline_start, 'days') <= daysUntilBeginning;
const isFirstWeek = differenceInDays(new Date(), toDate(course.timeline_start)) <= daysUntilBeginning;
if (isFirstWeek) {
const weekMeetingsIndex = emptyWeeksAtBeginning(weekMeetings);
thisWeekMeetings = weekMeetings[weekMeetingsIndex];
Expand Down