diff --git a/ts/components/conversation/TimelineItem.tsx b/ts/components/conversation/TimelineItem.tsx index fbf4d9cb9..e894053fd 100644 --- a/ts/components/conversation/TimelineItem.tsx +++ b/ts/components/conversation/TimelineItem.tsx @@ -4,9 +4,9 @@ import type { ReactChild, RefObject } from 'react'; import React from 'react'; import { omit } from 'lodash'; -import moment from 'moment'; import type { LocalizerType, ThemeType } from '../../types/Util'; +import { isSameDay } from '../../util/timestamp'; import type { InteractionModeType } from '../../state/ducks/conversations'; import { TimelineDateHeader } from './TimelineDateHeader'; @@ -344,7 +344,7 @@ export class TimelineItem extends React.PureComponent { previousItem && // This comparison avoids strange header behavior for out-of-order messages. item.timestamp > previousItem.timestamp && - !moment(previousItem.timestamp).isSame(item.timestamp, 'day') + !isSameDay(previousItem.timestamp, item.timestamp) ); if (shouldRenderDateHeader) { return ( diff --git a/ts/test-both/util/timestamp_test.ts b/ts/test-both/util/timestamp_test.ts index fbe374af3..9a4b88f9f 100644 --- a/ts/test-both/util/timestamp_test.ts +++ b/ts/test-both/util/timestamp_test.ts @@ -5,20 +5,21 @@ import { assert } from 'chai'; import * as sinon from 'sinon'; import moment from 'moment'; import type { LocalizerType } from '../../types/Util'; +import { HOUR, DAY } from '../../util/durations'; import { - isOlderThan, - isMoreRecentThan, - toDayMillis, formatDate, formatDateTimeLong, formatDateTimeShort, formatTime, + isMoreRecentThan, + isOlderThan, + isSameDay, + isToday, + toDayMillis, } from '../../util/timestamp'; const FAKE_NOW = new Date('2020-01-23T04:56:00.000'); -const ONE_HOUR = 3600 * 1000; -const ONE_DAY = 24 * ONE_HOUR; describe('timestamp', () => { function useFakeTimers() { @@ -229,28 +230,68 @@ describe('timestamp', () => { describe('isOlderThan', () => { it('returns false on recent and future timestamps', () => { - assert.isFalse(isOlderThan(Date.now(), ONE_DAY)); - assert.isFalse(isOlderThan(Date.now() + ONE_DAY, ONE_DAY)); + assert.isFalse(isOlderThan(Date.now(), DAY)); + assert.isFalse(isOlderThan(Date.now() + DAY, DAY)); }); it('returns true on old enough timestamps', () => { - assert.isFalse(isOlderThan(Date.now() - ONE_DAY + ONE_HOUR, ONE_DAY)); - assert.isTrue(isOlderThan(Date.now() - ONE_DAY - ONE_HOUR, ONE_DAY)); + assert.isFalse(isOlderThan(Date.now() - DAY + HOUR, DAY)); + assert.isTrue(isOlderThan(Date.now() - DAY - HOUR, DAY)); }); }); describe('isMoreRecentThan', () => { it('returns true on recent and future timestamps', () => { - assert.isTrue(isMoreRecentThan(Date.now(), ONE_DAY)); - assert.isTrue(isMoreRecentThan(Date.now() + ONE_DAY, ONE_DAY)); + assert.isTrue(isMoreRecentThan(Date.now(), DAY)); + assert.isTrue(isMoreRecentThan(Date.now() + DAY, DAY)); }); it('returns false on old enough timestamps', () => { - assert.isTrue(isMoreRecentThan(Date.now() - ONE_DAY + ONE_HOUR, ONE_DAY)); + assert.isTrue(isMoreRecentThan(Date.now() - DAY + HOUR, DAY)); + assert.isFalse(isMoreRecentThan(Date.now() - DAY - HOUR, DAY)); + }); + }); + + describe('isSameDay', () => { + it('returns false for different days', () => { assert.isFalse( - isMoreRecentThan(Date.now() - ONE_DAY - ONE_HOUR, ONE_DAY) + isSameDay( + new Date(1998, 10, 21, 12, 34, 56, 123), + new Date(2006, 10, 21, 12, 34, 56, 123) + ) ); }); + + it('returns true for identical timestamps', () => { + const timestamp = new Date(1998, 10, 21, 12, 34, 56, 123); + assert.isTrue(isSameDay(timestamp, timestamp)); + }); + + it('returns true for times on the same day', () => { + assert.isTrue( + isSameDay( + new Date(1998, 10, 21, 12, 34, 56, 123), + new Date(1998, 10, 21, 1, 23, 45, 123) + ) + ); + }); + }); + + describe('isToday', () => { + useFakeTimers(); + + it('returns false for days other than today', () => { + assert.isFalse(isToday(Date.now() + DAY)); + assert.isFalse(isToday(Date.now() - DAY)); + }); + + it('returns true right now', () => { + assert.isTrue(isToday(Date.now())); + }); + + it('returns true for times today', () => { + assert.isTrue(isToday(new Date('2020-01-23T03:56:00.000'))); + }); }); describe('toDayMillis', () => { diff --git a/ts/util/getMutedUntilText.ts b/ts/util/getMutedUntilText.ts index 858a42d99..30604f5db 100644 --- a/ts/util/getMutedUntilText.ts +++ b/ts/util/getMutedUntilText.ts @@ -1,8 +1,9 @@ -// Copyright 2021 Signal Messenger, LLC +// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import moment from 'moment'; import type { LocalizerType } from '../types/Util'; +import { isToday } from './timestamp'; /** * Returns something like "Muted until 6:09 PM", localized. @@ -18,7 +19,7 @@ export function getMutedUntilText( } const expires = moment(muteExpiresAt); - const muteExpirationUntil = moment().isSame(expires, 'day') + const muteExpirationUntil = isToday(expires) ? expires.format('LT') : expires.format('L, LT'); diff --git a/ts/util/timestamp.ts b/ts/util/timestamp.ts index 497e01364..693d4497e 100644 --- a/ts/util/timestamp.ts +++ b/ts/util/timestamp.ts @@ -31,10 +31,10 @@ export function toDayMillis(timestamp: number): number { return timestamp - (timestamp % DAY); } -const isSameDay = (a: RawTimestamp, b: RawTimestamp): boolean => +export const isSameDay = (a: RawTimestamp, b: RawTimestamp): boolean => moment(a).isSame(b, 'day'); -const isToday = (rawTimestamp: RawTimestamp): boolean => +export const isToday = (rawTimestamp: RawTimestamp): boolean => isSameDay(rawTimestamp, Date.now()); const isYesterday = (rawTimestamp: RawTimestamp): boolean =>