diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index eb109cc8a30e6..71c3dc6c9af35 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -78,6 +78,6 @@
+ android:value="com.expensify.chat.customairshipextender.CustomAirshipExtender" />
diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java
index 7156979a6c684..fd203ecc675d6 100644
--- a/android/app/src/main/java/com/expensify/chat/MainApplication.java
+++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java
@@ -2,24 +2,23 @@
import android.content.Context;
import android.database.CursorWindow;
+
import androidx.multidex.MultiDexApplication;
+
import com.expensify.chat.bootsplash.BootSplashPackage;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
-import com.reactnativecommunity.webview.RNCWebViewPackage;
-import com.reactnativecommunity.webview.RNCWebViewPackage;
-import com.reactnativecommunity.webview.RNCWebViewPackage;
-import com.existfragger.rnimagesize.RNImageSizePackage;
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags;
+import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.soloader.SoLoader;
-import java.lang.reflect.InvocationTargetException;
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
import java.util.List;
-import com.facebook.react.modules.i18nmanager.I18nUtil;
public class MainApplication extends MultiDexApplication implements ReactApplication {
@@ -111,13 +110,7 @@ private static void initializeFlipper(
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
+ } catch (Exception e) {
e.printStackTrace();
}
}
diff --git a/android/app/src/main/java/com/expensify/chat/StartupTimer.java b/android/app/src/main/java/com/expensify/chat/StartupTimer.java
index de43519fcb153..972e1dd95a0e1 100644
--- a/android/app/src/main/java/com/expensify/chat/StartupTimer.java
+++ b/android/app/src/main/java/com/expensify/chat/StartupTimer.java
@@ -1,4 +1,5 @@
package com.expensify.chat;
+
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
diff --git a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomAirshipExtender.java
similarity index 94%
rename from android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java
rename to android/app/src/main/java/com/expensify/chat/customairshipextender/CustomAirshipExtender.java
index dd09a934fc790..0cae0bd2de6d8 100644
--- a/android/app/src/main/java/com/expensify/chat/CustomAirshipExtender.java
+++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomAirshipExtender.java
@@ -1,4 +1,4 @@
-package com.expensify.chat;
+package com.expensify.chat.customairshipextender;
import android.content.Context;
import androidx.annotation.NonNull;
@@ -18,4 +18,4 @@ public void onAirshipReady(@NonNull Context context, @NonNull UAirship airship)
NotificationListener notificationListener = airship.getPushManager().getNotificationListener();
pushManager.setNotificationListener(new CustomNotificationListener(notificationListener, notificationProvider));
}
-}
\ No newline at end of file
+}
diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationListener.java
similarity index 97%
rename from android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java
rename to android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationListener.java
index d6fc1f9e1a350..514e5aca14a05 100644
--- a/android/app/src/main/java/com/expensify/chat/CustomNotificationListener.java
+++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationListener.java
@@ -1,4 +1,4 @@
-package com.expensify.chat;
+package com.expensify.chat.customairshipextender;
import androidx.annotation.NonNull;
import com.urbanairship.push.NotificationActionButtonInfo;
diff --git a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
similarity index 98%
rename from android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java
rename to android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
index 8e9f5082ef67b..deeff81bf76dc 100644
--- a/android/app/src/main/java/com/expensify/chat/CustomNotificationProvider.java
+++ b/android/app/src/main/java/com/expensify/chat/customairshipextender/CustomNotificationProvider.java
@@ -1,4 +1,4 @@
-package com.expensify.chat;
+package com.expensify.chat.customairshipextender;
import android.content.Context;
import android.graphics.Bitmap;
@@ -26,6 +26,8 @@
import com.urbanairship.util.ImageUtils;
import java.net.MalformedURLException;
import java.net.URL;
+import java.sql.Timestamp;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -126,7 +128,7 @@ private void applyMessageStyle(@NonNull Context context, NotificationCompat.Buil
String avatar = reportAction.get("avatar").getString();
String accountID = Integer.toString(reportAction.get("actorAccountID").getInt(-1));
String message = reportAction.get("message").getList().get(0).getMap().get("text").getString();
- long time = reportAction.get("timestamp").getLong(0);
+ long time = Timestamp.valueOf(reportAction.get("created").getString(Instant.now().toString())).getTime();
String roomName = payload.get("roomName") == null ? "" : payload.get("roomName").getString("");
String conversationTitle = "Chat with " + name;
if (!roomName.isEmpty()) {
diff --git a/src/components/withLocalize.js b/src/components/withLocalize.js
index 1fb7c9ca9dc8c..918c055948250 100755
--- a/src/components/withLocalize.js
+++ b/src/components/withLocalize.js
@@ -19,11 +19,11 @@ const withLocalizePropTypes = {
/** Formats number formatted according to locale and options */
numberFormat: PropTypes.func.isRequired,
- /** Converts a timestamp into a localized string representation that's relative to current moment in time */
- timestampToRelative: PropTypes.func.isRequired,
+ /** Converts a datetime into a localized string representation that's relative to current moment in time */
+ datetimeToRelative: PropTypes.func.isRequired,
- /** Formats a timestamp to local date and time string */
- timestampToDateTime: PropTypes.func.isRequired,
+ /** Formats a datetime to local date and time string */
+ datetimeToCalendarTime: PropTypes.func.isRequired,
/** Returns a locally converted phone number without the country code */
toLocalPhone: PropTypes.func.isRequired,
@@ -59,8 +59,8 @@ class LocaleContextProvider extends React.Component {
return {
translate: this.translate.bind(this),
numberFormat: this.numberFormat.bind(this),
- timestampToRelative: this.timestampToRelative.bind(this),
- timestampToDateTime: this.timestampToDateTime.bind(this),
+ datetimeToRelative: this.datetimeToRelative.bind(this),
+ datetimeToCalendarTime: this.datetimeToCalendarTime.bind(this),
fromLocalPhone: this.fromLocalPhone.bind(this),
toLocalPhone: this.toLocalPhone.bind(this),
fromLocaleDigit: this.fromLocaleDigit.bind(this),
@@ -88,22 +88,22 @@ class LocaleContextProvider extends React.Component {
}
/**
- * @param {Number} timestamp
+ * @param {String} datetime
* @returns {String}
*/
- timestampToRelative(timestamp) {
- return DateUtils.timestampToRelative(this.props.preferredLocale, timestamp);
+ datetimeToRelative(datetime) {
+ return DateUtils.datetimeToRelative(this.props.preferredLocale, datetime);
}
/**
- * @param {Number} timestamp
+ * @param {String} datetime - ISO-formatted datetime string
* @param {Boolean} [includeTimezone]
* @returns {String}
*/
- timestampToDateTime(timestamp, includeTimezone) {
- return DateUtils.timestampToDateTime(
+ datetimeToCalendarTime(datetime, includeTimezone) {
+ return DateUtils.datetimeToCalendarTime(
this.props.preferredLocale,
- timestamp,
+ datetime,
includeTimezone,
);
}
diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js
index 3582e672306d4..b3fbbbd03d8a5 100644
--- a/src/libs/DateUtils.js
+++ b/src/libs/DateUtils.js
@@ -33,27 +33,27 @@ Onyx.connect({
});
/**
- * Gets the user's stored time-zone NVP and returns a localized
- * Moment object for the given timestamp
+ * Gets the user's stored time zone NVP and returns a localized
+ * Moment object for the given ISO-formatted datetime string
*
* @param {String} locale
- * @param {Number} timestamp
+ * @param {String} datetime
* @param {String} [currentSelectedTimezone]
*
* @returns {Moment}
*
* @private
*/
-function getLocalMomentFromTimestamp(locale, timestamp, currentSelectedTimezone = timezone.selected) {
+function getLocalMomentFromDatetime(locale, datetime, currentSelectedTimezone = timezone.selected) {
moment.locale(locale);
- if (!timestamp) {
+ if (!datetime) {
return moment.tz(currentSelectedTimezone);
}
- return moment.unix(timestamp).tz(currentSelectedTimezone);
+ return moment.utc(datetime).tz(currentSelectedTimezone);
}
/**
- * Formats a timestamp to local date and time string
+ * Formats an ISO-formatted datetime string to local date and time string
*
* e.g.
*
@@ -61,13 +61,13 @@ function getLocalMomentFromTimestamp(locale, timestamp, currentSelectedTimezone
* Jan 20, 2019 at 5:30 PM anything over 1 year ago
*
* @param {String} locale
- * @param {Number} timestamp
+ * @param {String} datetime
* @param {Boolean} includeTimeZone
*
* @returns {String}
*/
-function timestampToDateTime(locale, timestamp, includeTimeZone = false) {
- const date = getLocalMomentFromTimestamp(locale, timestamp);
+function datetimeToCalendarTime(locale, datetime, includeTimeZone = false) {
+ const date = getLocalMomentFromDatetime(locale, datetime);
const tz = includeTimeZone ? ' [UTC]Z' : '';
const todayAt = Localize.translate(locale, 'common.todayAt');
@@ -86,7 +86,7 @@ function timestampToDateTime(locale, timestamp, includeTimeZone = false) {
}
/**
- * Converts a timestamp into a localized string representation
+ * Converts an ISO-formatted datetime string into a localized string representation
* that's relative to current moment in time.
*
* e.g.
@@ -99,12 +99,12 @@ function timestampToDateTime(locale, timestamp, includeTimeZone = false) {
* Jan 20, 2019 anything over 1 year
*
* @param {String} locale
- * @param {Number} timestamp
+ * @param {String} datetime
*
* @returns {String}
*/
-function timestampToRelative(locale, timestamp) {
- const date = getLocalMomentFromTimestamp(locale, timestamp);
+function datetimeToRelative(locale, datetime) {
+ const date = getLocalMomentFromDatetime(locale, datetime);
return moment(date).fromNow();
}
@@ -161,18 +161,29 @@ function getMicroseconds() {
return Date.now() * CONST.MICROSECONDS_PER_MS;
}
+/**
+ * Returns the current time in milliseconds in the format expected by the database
+ * @returns {String}
+ */
+function currentDBTime() {
+ return new Date().toISOString()
+ .replace('T', ' ')
+ .replace('Z', '');
+}
+
/**
* @namespace DateUtils
*/
const DateUtils = {
- timestampToRelative,
- timestampToDateTime,
+ datetimeToRelative,
+ datetimeToCalendarTime,
startCurrentDateUpdater,
- getLocalMomentFromTimestamp,
+ getLocalMomentFromDatetime,
getCurrentTimezone,
canUpdateTimezone,
setTimezoneUpdated,
getMicroseconds,
+ currentDBTime,
};
export default DateUtils;
diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js
index ae04dc86c4943..2876d36ebefb4 100644
--- a/src/libs/ReportActionsUtils.js
+++ b/src/libs/ReportActionsUtils.js
@@ -3,6 +3,7 @@ import _ from 'underscore';
import lodashMerge from 'lodash/merge';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import Onyx from 'react-native-onyx';
+import moment from 'moment';
import * as CollectionUtils from './CollectionUtils';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';
@@ -83,7 +84,7 @@ function isConsecutiveActionMadeByPreviousActor(reportActions, actionIndex) {
}
// Comments are only grouped if they happen within 5 minutes of each other
- if (currentAction.action.timestamp - previousAction.action.timestamp > 300) {
+ if (moment(currentAction.action.created).unix() - moment(previousAction.action.created).unix() > 300) {
return false;
}
diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js
index 9252911b4535f..0d899070a2de0 100644
--- a/src/libs/ReportUtils.js
+++ b/src/libs/ReportUtils.js
@@ -2,7 +2,6 @@ import _ from 'underscore';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
-import moment from 'moment';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
@@ -15,6 +14,7 @@ import ROUTES from '../ROUTES';
import * as NumberUtils from './NumberUtils';
import * as NumberFormatUtils from './NumberFormatUtils';
import Permissions from './Permissions';
+import DateUtils from './DateUtils';
let sessionEmail;
Onyx.connect({
@@ -643,7 +643,7 @@ function buildOptimisticReportAction(sequenceNumber, text, file) {
sequenceNumber,
clientID: NumberUtils.generateReportActionClientID(),
avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)),
- timestamp: moment().unix(),
+ created: DateUtils.currentDBTime(),
message: [
{
type: CONST.REPORT.MESSAGE.TYPE.COMMENT,
@@ -795,7 +795,7 @@ function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency,
reportActionID: NumberUtils.rand64(),
sequenceNumber,
shouldShow: true,
- timestamp: moment().unix(),
+ created: DateUtils.currentDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}
@@ -883,7 +883,7 @@ function buildOptimisticCreatedReportAction(ownerEmail) {
automatic: false,
sequenceNumber: 0,
avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)),
- timestamp: moment().unix(),
+ created: DateUtils.currentDBTime(),
shouldShow: true,
},
};
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 49dddb0cf0949..5462f203ed985 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1323,12 +1323,12 @@ Onyx.connect({
return;
}
- if (!action.timestamp) {
+ if (!action.created) {
return;
}
// If we are past the deadline to notify for this comment don't do it
- if (moment.utc(action.timestamp * 1000).isBefore(moment.utc().subtract(10, 'seconds'))) {
+ if (moment.utc(moment(action.created).unix() * 1000).isBefore(moment.utc().subtract(10, 'seconds'))) {
handledReportActions[reportID] = handledReportActions[reportID] || {};
handledReportActions[reportID][action.sequenceNumber] = true;
return;
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index 7ca5afb9c8571..fc570c0c5d79d 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -95,7 +95,7 @@ class DetailsPage extends React.PureComponent {
// If we have a reportID param this means that we
// arrived here via the ParticipantsPage and should be allowed to navigate back to it
const shouldShowBackButton = Boolean(this.props.route.params.reportID);
- const timezone = details.timezone ? DateUtils.getLocalMomentFromTimestamp(this.props.preferredLocale, null, details.timezone.selected) : null;
+ const timezone = details.timezone ? DateUtils.getLocalMomentFromDatetime(this.props.preferredLocale, null, details.timezone.selected) : null;
const GMTTime = timezone ? `${timezone.toString().split(/[+-]/)[0].slice(-3)} ${timezone.zoneAbbr()}` : '';
const currentTime = (timezone && Number.isNaN(Number(timezone.zoneAbbr()))) ? timezone.zoneAbbr() : GMTTime;
const shouldShowLocalTime = !ReportUtils.hasExpensifyEmails([details.login]);
diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.js
index b62a7ba5e833e..a93d2e57adcc1 100644
--- a/src/pages/home/report/ParticipantLocalTime.js
+++ b/src/pages/home/report/ParticipantLocalTime.js
@@ -42,8 +42,8 @@ class ParticipantLocalTime extends PureComponent {
getParticipantLocalTime() {
const reportRecipientTimezone = lodashGet(this.props.participant, 'timezone', CONST.DEFAULT_TIME_ZONE);
- const reportTimezone = DateUtils.getLocalMomentFromTimestamp(this.props.preferredLocale, null, reportRecipientTimezone.selected);
- const currentTimezone = DateUtils.getLocalMomentFromTimestamp(this.props.preferredLocale);
+ const reportTimezone = DateUtils.getLocalMomentFromDatetime(this.props.preferredLocale, null, reportRecipientTimezone.selected);
+ const currentTimezone = DateUtils.getLocalMomentFromDatetime(this.props.preferredLocale);
const reportRecipientDay = reportTimezone.format('dddd');
const currentUserDay = currentTimezone.format('dddd');
diff --git a/src/pages/home/report/ReportActionItemDate.js b/src/pages/home/report/ReportActionItemDate.js
index 8ee127a721fd9..0cf570132bd9e 100644
--- a/src/pages/home/report/ReportActionItemDate.js
+++ b/src/pages/home/report/ReportActionItemDate.js
@@ -8,13 +8,13 @@ import {withCurrentDate} from '../../../components/OnyxProvider';
const propTypes = {
/** UTC timestamp for when the action was created */
- timestamp: PropTypes.number.isRequired,
+ created: PropTypes.string.isRequired,
...withLocalizePropTypes,
};
const ReportActionItemDate = props => (
- {props.timestampToDateTime(props.timestamp)}
+ {props.datetimeToCalendarTime(props.created)}
);
diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js
index 12c09b5f934f0..4ca290a8912a5 100644
--- a/src/pages/home/report/ReportActionItemSingle.js
+++ b/src/pages/home/report/ReportActionItemSingle.js
@@ -98,7 +98,7 @@ const ReportActionItemSingle = (props) => {
/>
))}
-
+
) : null}
{props.children}
diff --git a/src/pages/home/report/reportActionPropTypes.js b/src/pages/home/report/reportActionPropTypes.js
index d752047cbb970..d3cebe8ba317f 100644
--- a/src/pages/home/report/reportActionPropTypes.js
+++ b/src/pages/home/report/reportActionPropTypes.js
@@ -12,8 +12,8 @@ export default {
/** ID of the report action */
sequenceNumber: PropTypes.number,
- /** Unix timestamp */
- timestamp: PropTypes.number,
+ /** ISO-formatted datetime */
+ created: PropTypes.string,
/** report action message */
message: PropTypes.arrayOf(reportActionFragmentPropTypes),
diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js
index cbf04d3adb582..f36c7b5560ae4 100644
--- a/tests/actions/ReportTest.js
+++ b/tests/actions/ReportTest.js
@@ -1,7 +1,6 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
-import moment from 'moment';
import {
beforeEach, beforeAll, afterEach, jest, describe, it, expect,
} from '@jest/globals';
@@ -17,6 +16,7 @@ import Log from '../../src/libs/Log';
import * as PersistedRequests from '../../src/libs/actions/PersistedRequests';
import * as User from '../../src/libs/actions/User';
import * as ReportUtils from '../../src/libs/ReportUtils';
+import DateUtils from '../../src/libs/DateUtils';
describe('actions/Report', () => {
beforeAll(() => {
@@ -260,7 +260,7 @@ describe('actions/Report', () => {
person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
sequenceNumber: 1,
shouldShow: true,
- timestamp: moment().unix(),
+ created: DateUtils.currentDBTime(),
},
},
},
@@ -325,7 +325,7 @@ describe('actions/Report', () => {
avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
shouldShow: true,
- timestamp: moment().unix(),
+ created: DateUtils.currentDBTime(),
reportActionID: 'derp',
};
diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js
index cafe2d1a7cb90..7796e66f27dd4 100644
--- a/tests/ui/UnreadIndicatorsTest.js
+++ b/tests/ui/UnreadIndicatorsTest.js
@@ -107,6 +107,7 @@ const USER_B_ACCOUNT_ID = 2;
const USER_B_EMAIL = 'user_b@test.com';
const USER_C_ACCOUNT_ID = 3;
const USER_C_EMAIL = 'user_c@test.com';
+const MOMENT_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
/**
* Sets up a test with a logged in user that has one unread chat from another user. Returns the test instance.
@@ -141,18 +142,18 @@ function signInAndGetAppWithUnreadChat() {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
automatic: false,
sequenceNumber: 0,
- timestamp: MOMENT_TEN_MINUTES_AGO.unix(),
+ created: MOMENT_TEN_MINUTES_AGO.format(MOMENT_FORMAT),
reportActionID: NumberUtils.rand64(),
},
- 1: TestHelper.buildTestReportComment(USER_B_EMAIL, 1, MOMENT_TEN_MINUTES_AGO.add(10, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 2: TestHelper.buildTestReportComment(USER_B_EMAIL, 2, MOMENT_TEN_MINUTES_AGO.add(20, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 3: TestHelper.buildTestReportComment(USER_B_EMAIL, 3, MOMENT_TEN_MINUTES_AGO.add(30, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 4: TestHelper.buildTestReportComment(USER_B_EMAIL, 4, MOMENT_TEN_MINUTES_AGO.add(40, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 5: TestHelper.buildTestReportComment(USER_B_EMAIL, 5, MOMENT_TEN_MINUTES_AGO.add(50, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 6: TestHelper.buildTestReportComment(USER_B_EMAIL, 6, MOMENT_TEN_MINUTES_AGO.add(60, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 7: TestHelper.buildTestReportComment(USER_B_EMAIL, 7, MOMENT_TEN_MINUTES_AGO.add(70, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 8: TestHelper.buildTestReportComment(USER_B_EMAIL, 8, MOMENT_TEN_MINUTES_AGO.add(80, 'seconds').unix(), USER_B_ACCOUNT_ID),
- 9: TestHelper.buildTestReportComment(USER_B_EMAIL, 9, MOMENT_TEN_MINUTES_AGO.add(90, 'seconds').unix(), USER_B_ACCOUNT_ID),
+ 1: TestHelper.buildTestReportComment(USER_B_EMAIL, 1, MOMENT_TEN_MINUTES_AGO.add(10, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 2: TestHelper.buildTestReportComment(USER_B_EMAIL, 2, MOMENT_TEN_MINUTES_AGO.add(20, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 3: TestHelper.buildTestReportComment(USER_B_EMAIL, 3, MOMENT_TEN_MINUTES_AGO.add(30, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 4: TestHelper.buildTestReportComment(USER_B_EMAIL, 4, MOMENT_TEN_MINUTES_AGO.add(40, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 5: TestHelper.buildTestReportComment(USER_B_EMAIL, 5, MOMENT_TEN_MINUTES_AGO.add(50, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 6: TestHelper.buildTestReportComment(USER_B_EMAIL, 6, MOMENT_TEN_MINUTES_AGO.add(60, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 7: TestHelper.buildTestReportComment(USER_B_EMAIL, 7, MOMENT_TEN_MINUTES_AGO.add(70, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 8: TestHelper.buildTestReportComment(USER_B_EMAIL, 8, MOMENT_TEN_MINUTES_AGO.add(80, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
+ 9: TestHelper.buildTestReportComment(USER_B_EMAIL, 9, MOMENT_TEN_MINUTES_AGO.add(90, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID),
});
Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, {
[USER_B_EMAIL]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'),
@@ -276,7 +277,7 @@ describe('Unread Indicators', () => {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
automatic: false,
sequenceNumber: 0,
- timestamp: NEW_REPORT_CREATED_MOMENT.unix(),
+ created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT),
reportActionID: NumberUtils.rand64(),
},
1: {
@@ -285,7 +286,7 @@ describe('Unread Indicators', () => {
actorAccountID: USER_C_ACCOUNT_ID,
person: [{type: 'TEXT', style: 'strong', text: 'User C'}],
sequenceNumber: 1,
- timestamp: NEW_REPORT_CREATED_MOMENT.add(5, 'seconds').unix(),
+ created: NEW_REPORT_CREATED_MOMENT.add(5, 'seconds').format(MOMENT_FORMAT),
message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}],
reportActionID: NumberUtils.rand64(),
},
diff --git a/tests/unit/DateUtilsTest.js b/tests/unit/DateUtilsTest.js
new file mode 100644
index 0000000000000..7724162b8288d
--- /dev/null
+++ b/tests/unit/DateUtilsTest.js
@@ -0,0 +1,54 @@
+import moment from 'moment';
+import Onyx from 'react-native-onyx';
+import DateUtils from '../../src/libs/DateUtils';
+import ONYXKEYS from '../../src/ONYXKEYS';
+import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
+
+const LOCALE = 'en';
+
+describe('DateUtils', () => {
+ beforeAll(() => {
+ Onyx.init({
+ keys: ONYXKEYS,
+ initialKeyStates: {
+ [ONYXKEYS.SESSION]: {email: 'current@user.com'},
+ [ONYXKEYS.PERSONAL_DETAILS]: {'current@user.com': {timezone: {selected: 'Etc/UTC'}}},
+ },
+ });
+ return waitForPromisesToResolve();
+ });
+
+ const datetime = '2022-11-07 00:00:00';
+ it('should return a moment object with the formatted datetime when calling getLocalMomentFromDatetime', () => {
+ const localMoment = DateUtils.getLocalMomentFromDatetime(LOCALE, datetime, 'America/Los_Angeles');
+ expect(moment.isMoment(localMoment)).toBe(true);
+ expect(moment(localMoment).format()).toEqual('2022-11-06T16:00:00-08:00');
+ });
+
+ it('should return the date in calendar time when calling datetimeToCalendarTime', () => {
+ const today = moment.utc().set({hour: 14, minute: 32});
+ expect(DateUtils.datetimeToCalendarTime(LOCALE, today)).toBe('Today at 2:32 PM');
+
+ const yesterday = moment.utc().subtract(1, 'days').set({hour: 7, minute: 43});
+ expect(DateUtils.datetimeToCalendarTime(LOCALE, yesterday)).toBe('Yesterday at 7:43 AM');
+
+ const date = moment.utc('2022-11-05').set({hour: 10, minute: 17});
+ expect(DateUtils.datetimeToCalendarTime(LOCALE, date)).toBe('Nov 5 at 10:17 AM');
+ });
+
+ it('should return the date in calendar time when calling datetimeToRelative', () => {
+ const aFewSecondsAgo = moment().subtract(10, 'seconds');
+ expect(DateUtils.datetimeToRelative(LOCALE, aFewSecondsAgo)).toBe('a few seconds ago');
+
+ const aMinuteAgo = moment().subtract(1, 'minute');
+ expect(DateUtils.datetimeToRelative(LOCALE, aMinuteAgo)).toBe('a minute ago');
+
+ const anHourAgo = moment().subtract(1, 'hour');
+ expect(DateUtils.datetimeToRelative(LOCALE, anHourAgo)).toBe('an hour ago');
+ });
+
+ it('should return the date in the format expected by the database when calling currentDBTime', () => {
+ const currentDBTime = DateUtils.currentDBTime();
+ expect(currentDBTime).toBe(moment(currentDBTime).format('YYYY-MM-DD HH:mm:ss.SSS'));
+ });
+});
diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js
index 0025ff9040bdc..7b4f05c16e34e 100644
--- a/tests/utils/TestHelper.js
+++ b/tests/utils/TestHelper.js
@@ -157,17 +157,17 @@ function setPersonalDetails(login, accountID) {
/**
* @param {String} actorEmail
* @param {Number} sequenceNumber
- * @param {Number} timestamp
+ * @param {String} created
* @param {Number} actorAccountID
* @returns {Object}
*/
-function buildTestReportComment(actorEmail, sequenceNumber, timestamp, actorAccountID) {
+function buildTestReportComment(actorEmail, sequenceNumber, created, actorAccountID) {
return {
actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorEmail,
person: [{type: 'TEXT', style: 'strong', text: 'User B'}],
sequenceNumber,
- timestamp,
+ created,
message: [{type: 'COMMENT', html: `Comment ${sequenceNumber}`, text: `Comment ${sequenceNumber}`}],
reportActionID: NumberUtils.rand64(),
actorAccountID,