diff --git a/frontend/app/javascript/shared/helpers/specs/timeHelper.spec.js b/frontend/app/javascript/shared/helpers/specs/timeHelper.spec.js index 4bb12718..19240a25 100644 --- a/frontend/app/javascript/shared/helpers/specs/timeHelper.spec.js +++ b/frontend/app/javascript/shared/helpers/specs/timeHelper.spec.js @@ -6,6 +6,7 @@ import { shortTimestamp, getDayDifferenceFromNow, hasOneDayPassed, + isNewerTimestamp, } from 'shared/helpers/timeHelper'; import { zhCN } from 'date-fns/locale'; @@ -20,6 +21,25 @@ afterEach(() => { vi.useRealTimers(); }); +describe('#isNewerTimestamp', () => { + it.each([ + ['Unix seconds', 1_700_000_000, 1_700_000_001], + ['numeric strings', '1700000000', '1700000001'], + ['early Unix milliseconds', 978_307_200_000, 1_700_000_000], + ['below the former 1e12 threshold', 999_999_999_999, 1_000_000_000_000], + ])('orders %s', (_label, current, next) => { + expect(isNewerTimestamp(current, next)).toBe(true); + }); + + it('preserves missing and invalid timestamp compatibility', () => { + expect(isNewerTimestamp(null, 'invalid')).toBe(true); + expect(isNewerTimestamp(1_700_000_000, null)).toBe(true); + expect(isNewerTimestamp('invalid', 1_700_000_000)).toBe(true); + expect(isNewerTimestamp(1_700_000_000, 'invalid')).toBe(false); + expect(isNewerTimestamp(1_700_000_000, 9e15)).toBe(false); + }); +}); + describe('#messageStamp', () => { it('returns correct value', () => { expect(messageStamp(1612971343)).toEqual('3:35 PM'); diff --git a/frontend/app/javascript/shared/helpers/timeHelper.js b/frontend/app/javascript/shared/helpers/timeHelper.js index 3f02cb42..529340c1 100644 --- a/frontend/app/javascript/shared/helpers/timeHelper.js +++ b/frontend/app/javascript/shared/helpers/timeHelper.js @@ -6,11 +6,20 @@ import { differenceInDays, } from 'date-fns'; +// Numeric realtime timestamps below 1e10 are Unix seconds (through 2286-11-20); +// larger values are milliseconds, bounded by the ECMAScript Date range. +const UNIX_SECONDS_LIMIT = 1e10; +const DATE_MILLISECONDS_LIMIT = 8.64e15; + const normalizeTimestamp = value => { const numericValue = typeof value === 'string' && value.trim() ? Number(value) : value; if (typeof numericValue === 'number' && Number.isFinite(numericValue)) { - return Math.abs(numericValue) < 1e12 ? numericValue * 1000 : numericValue; + const timestamp = + Math.abs(numericValue) < UNIX_SECONDS_LIMIT + ? numericValue * 1000 + : numericValue; + return Math.abs(timestamp) <= DATE_MILLISECONDS_LIMIT ? timestamp : null; } if (typeof value !== 'string') return null;