Migrate to moment to fix timestamps based on timezone or local time (#3126)

This commit is contained in:
Elias Nahum 2019-08-15 12:32:16 -04:00 committed by GitHub
parent d4bd7833ea
commit 0856d4b517
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 91 additions and 131 deletions

View file

@ -3,35 +3,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import {injectIntl, intlShape} from 'react-intl';
import moment from 'moment-timezone';
import {Text} from 'react-native';
class FormattedDate extends React.PureComponent {
export default class FormattedDate extends React.PureComponent {
static propTypes = {
intl: intlShape.isRequired,
value: PropTypes.any.isRequired,
format: PropTypes.string,
children: PropTypes.func,
timeZone: PropTypes.string,
value: PropTypes.any.isRequired,
};
static defaultProps = {
format: 'ddd, MMM DD, YYYY',
};
render() {
const {
intl,
format,
timeZone,
value,
children,
...props
} = this.props;
Reflect.deleteProperty(props, 'format');
const formattedDate = intl.formatDate(value, this.props);
if (typeof children === 'function') {
return children(formattedDate);
let formattedDate = moment(value).format(format);
if (timeZone) {
formattedDate = moment.tz(value, timeZone).format(format);
}
return <Text {...props}>{formattedDate}</Text>;
}
}
export default injectIntl(FormattedDate);

View file

@ -3,7 +3,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {Text} from 'react-native';
import moment from 'moment-timezone';
@ -15,30 +14,19 @@ export default class FormattedTime extends React.PureComponent {
hour12: PropTypes.bool,
};
static contextTypes = {
intl: intlShape.isRequired,
};
getFormattedTime = () => {
const {intl} = this.context;
const {
value,
timeZone,
hour12,
} = this.props;
const format = hour12 ? 'hh:mm A' : 'HH:mm';
if (timeZone) {
const format = hour12 ? 'hh:mm A' : 'HH:mm';
return moment.tz(value, timeZone).format(format);
}
// If no timezone is defined fallback to the previous implementation
return intl.formatDate(new Date(value), {
hour: 'numeric',
minute: 'numeric',
hour12,
});
return moment(value).format(format);
};
render() {

View file

@ -218,7 +218,10 @@ export default class PostHeader extends PureComponent {
dateComponent = (
<View style={style.datetime}>
<Text style={style.time}>
<FormattedDate value={createAt}/>
<FormattedDate
timeZone={userTimezone}
value={createAt}
/>
</Text>
<Text style={style.time}>
<FormattedTime

View file

@ -31,9 +31,8 @@ exports[`DateHeader component should match snapshot when timezone is set 1`] = `
}
}
>
<InjectIntl(FormattedDate)
day="2-digit"
month="short"
<FormattedDate
format="ddd, MMM DD, YYYY"
style={
Object {
"color": "#3d3c40",
@ -41,9 +40,8 @@ exports[`DateHeader component should match snapshot when timezone is set 1`] = `
"fontWeight": "600",
}
}
value={1970-01-18T17:19:12.392Z}
weekday="short"
year="numeric"
timeZone="America/New_York"
value={1531152392}
/>
</View>
<View
@ -90,9 +88,8 @@ exports[`DateHeader component should match snapshot with suffix 1`] = `
}
}
>
<InjectIntl(FormattedDate)
day="2-digit"
month="short"
<FormattedDate
format="ddd, MMM DD, YYYY"
style={
Object {
"color": "#3d3c40",
@ -100,9 +97,8 @@ exports[`DateHeader component should match snapshot with suffix 1`] = `
"fontWeight": "600",
}
}
value={1970-01-18T17:19:12.392Z}
weekday="short"
year="numeric"
timeZone={null}
value={1531152392}
/>
</View>
<View
@ -149,9 +145,8 @@ exports[`DateHeader component should match snapshot without suffix 1`] = `
}
}
>
<InjectIntl(FormattedDate)
day="2-digit"
month="short"
<FormattedDate
format="ddd, MMM DD, YYYY"
style={
Object {
"color": "#3d3c40",
@ -159,9 +154,8 @@ exports[`DateHeader component should match snapshot without suffix 1`] = `
"fontWeight": "600",
}
}
value={1970-01-18T17:19:12.392Z}
weekday="short"
year="numeric"
timeZone={null}
value={1531152392}
/>
</View>
<View

View file

@ -7,7 +7,6 @@ import {
View,
ViewPropTypes,
} from 'react-native';
import moment from 'moment-timezone';
import FormattedDate from 'app/components/formatted_date';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@ -25,25 +24,14 @@ export default class DateHeader extends PureComponent {
const {theme, timeZone, date} = this.props;
const style = getStyleSheet(theme);
const dateFormatProps = {
weekday: 'short',
day: '2-digit',
month: 'short',
year: 'numeric',
value: new Date(date),
};
if (timeZone) {
dateFormatProps.value = moment.tz(date, timeZone).toDate();
}
return (
<View style={[style.container, this.props.style]}>
<View style={style.line}/>
<View style={style.dateContainer}>
<FormattedDate
style={style.date}
{...dateFormatProps}
timeZone={timeZone}
value={date}
/>
</View>
<View style={style.line}/>

View file

@ -4,6 +4,7 @@
import 'intl';
import {addLocaleData} from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
import moment from 'moment';
import en from 'assets/i18n/en.json';
@ -16,72 +17,92 @@ addLocaleData(enLocaleData);
function loadTranslation(locale) {
try {
let localeData;
let momentData;
switch (locale) {
case 'de':
TRANSLATIONS.de = require('assets/i18n/de.json');
localeData = require('react-intl/locale-data/de');
momentData = require('moment/locale/de');
break;
case 'es':
TRANSLATIONS.es = require('assets/i18n/es.json');
localeData = require('react-intl/locale-data/es');
momentData = require('moment/locale/es');
break;
case 'fr':
TRANSLATIONS.fr = require('assets/i18n/fr.json');
localeData = require('react-intl/locale-data/fr');
momentData = require('moment/locale/fr');
break;
case 'it':
TRANSLATIONS.it = require('assets/i18n/it.json');
localeData = require('react-intl/locale-data/it');
momentData = require('moment/locale/it');
break;
case 'ja':
TRANSLATIONS.ja = require('assets/i18n/ja.json');
localeData = require('react-intl/locale-data/ja');
momentData = require('moment/locale/ja');
break;
case 'ko':
TRANSLATIONS.ko = require('assets/i18n/ko.json');
localeData = require('react-intl/locale-data/ko');
momentData = require('moment/locale/ko');
break;
case 'nl':
TRANSLATIONS.nl = require('assets/i18n/nl.json');
localeData = require('react-intl/locale-data/nl');
momentData = require('moment/locale/nl');
break;
case 'pl':
TRANSLATIONS.pl = require('assets/i18n/pl.json');
localeData = require('react-intl/locale-data/pl');
momentData = require('moment/locale/pl');
break;
case 'pt-BR':
TRANSLATIONS[locale] = require('assets/i18n/pt-BR.json');
localeData = require('react-intl/locale-data/pt');
momentData = require('moment/locale/pt-br');
break;
case 'ro':
TRANSLATIONS.ro = require('assets/i18n/ro.json');
localeData = require('react-intl/locale-data/ro');
momentData = require('moment/locale/ro');
break;
case 'ru':
TRANSLATIONS.ru = require('assets/i18n/ru.json');
localeData = require('react-intl/locale-data/ru');
momentData = require('moment/locale/ru');
break;
case 'tr':
TRANSLATIONS.tr = require('assets/i18n/tr.json');
localeData = require('react-intl/locale-data/tr');
momentData = require('moment/locale/tr');
break;
case 'uk':
TRANSLATIONS.tr = require('assets/i18n/uk.json');
localeData = require('react-intl/locale-data/uk');
momentData = require('moment/locale/uk');
break;
case 'zh-CN':
TRANSLATIONS[locale] = require('assets/i18n/zh-CN.json');
localeData = require('react-intl/locale-data/zh');
momentData = require('moment/locale/zh-cn');
break;
case 'zh-TW':
TRANSLATIONS[locale] = require('assets/i18n/zh-TW.json');
localeData = require('react-intl/locale-data/zh');
momentData = require('moment/locale/zh-tw');
break;
}
if (localeData) {
addLocaleData(localeData);
}
if (momentData) {
moment.updateLocale(locale, momentData);
}
} catch (e) {
console.error('NO Translation found', e); //eslint-disable-line no-console
}

View file

@ -1,13 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`channel_info_header should match snapshot 1`] = `
<SafeAreaView
style={
Object {
"backgroundColor": "white",
}
}
>
<SafeAreaView>
<View
style={
Object {
@ -404,11 +398,9 @@ exports[`channel_info_header should match snapshot 1`] = `
}
}
/>
<InjectIntl(FormattedDate)
day="2-digit"
month="long"
value={1970-01-01T00:00:00.123Z}
year="numeric"
<FormattedDate
format="LL"
value={123}
/>
</Text>
</View>
@ -416,13 +408,7 @@ exports[`channel_info_header should match snapshot 1`] = `
`;
exports[`channel_info_header should match snapshot when DM and hasGuests 1`] = `
<SafeAreaView
style={
Object {
"backgroundColor": "white",
}
}
>
<SafeAreaView>
<View
style={
Object {
@ -847,11 +833,9 @@ exports[`channel_info_header should match snapshot when DM and hasGuests 1`] = `
}
}
/>
<InjectIntl(FormattedDate)
day="2-digit"
month="long"
value={1970-01-01T00:00:00.123Z}
year="numeric"
<FormattedDate
format="LL"
value={123}
/>
</Text>
</View>
@ -859,13 +843,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests 1`] = `
`;
exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
<SafeAreaView
style={
Object {
"backgroundColor": "white",
}
}
>
<SafeAreaView>
<View
style={
Object {
@ -1290,11 +1268,9 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
}
}
/>
<InjectIntl(FormattedDate)
day="2-digit"
month="long"
value={1970-01-01T00:00:00.123Z}
year="numeric"
<FormattedDate
format="LL"
value={123}
/>
</Text>
</View>
@ -1302,13 +1278,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
`;
exports[`channel_info_header should match snapshot when is group constrained 1`] = `
<SafeAreaView
style={
Object {
"backgroundColor": "white",
}
}
>
<SafeAreaView>
<View
style={
Object {
@ -1726,11 +1696,9 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
}
}
/>
<InjectIntl(FormattedDate)
day="2-digit"
month="long"
value={1970-01-01T00:00:00.123Z}
year="numeric"
<FormattedDate
format="LL"
value={123}
/>
</Text>
</View>
@ -1738,13 +1706,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
`;
exports[`channel_info_header should match snapshot when public channel and hasGuests 1`] = `
<SafeAreaView
style={
Object {
"backgroundColor": "white",
}
}
>
<SafeAreaView>
<View
style={
Object {
@ -2169,11 +2131,9 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
}
}
/>
<InjectIntl(FormattedDate)
day="2-digit"
month="long"
value={1970-01-01T00:00:00.123Z}
year="numeric"
<FormattedDate
format="LL"
value={123}
/>
</Text>
</View>

View file

@ -41,6 +41,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
isBot: PropTypes.bool.isRequired,
hasGuests: PropTypes.bool.isRequired,
isGroupConstrained: PropTypes.bool,
timeZone: PropTypes.string,
};
static contextTypes = {
@ -135,6 +136,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
isArchived,
isBot,
isGroupConstrained,
timeZone,
} = this.props;
const style = getStyleSheet(theme);
@ -145,7 +147,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
style.detail;
return (
<SafeAreaView style={style.safeAreaView}>
<SafeAreaView>
<View style={style.container}>
<View style={[style.channelNameContainer, style.row]}>
<ChannelIcon
@ -231,10 +233,9 @@ export default class ChannelInfoHeader extends React.PureComponent {
}}
/>
<FormattedDate
value={new Date(createAt)}
year='numeric'
month='long'
day='2-digit'
format='LL'
timeZone={timeZone}
value={createAt}
/>
</Text>
}
@ -287,8 +288,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
row: {
paddingHorizontal: 15,
},
safeAreaView: {
backgroundColor: 'white',
},
};
});

View file

@ -29,7 +29,8 @@ import {getCurrentUserId, getUser, getStatusForUserId, getCurrentUserRoles} from
import {areChannelMentionsIgnored, getUserIdFromChannelName, isChannelMuted, showDeleteOption, showManagementOptions} from 'mattermost-redux/utils/channel_utils';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {isGuest} from 'app/utils/users';
import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
import {
popTopScreen,
@ -47,6 +48,8 @@ import {
setChannelDisplayName,
} from 'app/actions/views/channel';
import {isLandscape} from 'app/selectors/device';
import {isGuest} from 'app/utils/users';
import ChannelInfo from './channel_info';
function mapStateToProps(state) {
@ -93,6 +96,12 @@ function mapStateToProps(state) {
const canEditChannel = !channelIsReadOnly && showManagementOptions(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin);
const viewArchivedChannels = config.ExperimentalViewArchivedChannels === 'true';
const enableTimezone = isTimezoneEnabled(state);
let timeZone = null;
if (enableTimezone) {
timeZone = getUserCurrentTimezone(currentUser.timezone);
}
return {
canDeleteChannel: showDeleteOption(state, config, license, currentChannel, isAdmin, isSystemAdmin, isChannelAdmin),
viewArchivedChannels,
@ -112,6 +121,7 @@ function mapStateToProps(state) {
canManageUsers,
isBot,
isLandscape: isLandscape(state),
timeZone,
};
}