Migrate to moment to fix timestamps based on timezone or local time (#3126)
This commit is contained in:
parent
d4bd7833ea
commit
0856d4b517
9 changed files with 91 additions and 131 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue