From 69a9b2ecc5f314389c9b8f277427c7c0265833b8 Mon Sep 17 00:00:00 2001 From: Jan Aagaard Date: Tue, 18 Jun 2024 00:35:37 +0200 Subject: [PATCH] Create a testable version of ago() and re-enable the disabled test (#4364) * Enable the test of ago() * Use test cases This puts the input and the expected values next to each other. * Create dateDiff function This is a copy of ago(), but with the ability to specify the second date instead of using Date.now(). * Let ago() use dateDiff() * Move constants close to usage * Test dateDiff instead of ago This makes it possible to test the dates without being forced to rely on what the current date is. The commented out tests do not yet pass. This is fixed in later commits. * Update dateDiff and enable the remaining tests --- __tests__/lib/string.test.ts | 149 +++++++++++++++++++---------------- src/lib/strings/time.ts | 49 +++++++----- 2 files changed, 108 insertions(+), 90 deletions(-) diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 78478a26d4..b8fc6f508f 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -6,7 +6,7 @@ import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' import {enforceLen} from '../../src/lib/strings/helpers' import {detectLinkables} from '../../src/lib/strings/rich-text-detection' import {shortenLinks} from '../../src/lib/strings/rich-text-manip' -import {ago} from '../../src/lib/strings/time' +import {dateDiff} from '../../src/lib/strings/time' import { makeRecordUri, toNiceDomain, @@ -142,77 +142,86 @@ describe('makeRecordUri', () => { }) }) -// FIXME: Reenable after fixing non-deterministic test. -describe.skip('ago', () => { - const oneYearDate = new Date( - new Date().setMonth(new Date().getMonth() - 11), - ).setDate(new Date().getDate() - 28) - - const inputs = [ - 1671461038, - '04 Dec 1995 00:12:00 GMT', - new Date(), - new Date().setSeconds(new Date().getSeconds() - 10), - new Date().setMinutes(new Date().getMinutes() - 10), - new Date().setHours(new Date().getHours() - 1), - new Date().setDate(new Date().getDate() - 1), - new Date().setDate(new Date().getDate() - 20), - new Date().setDate(new Date().getDate() - 25), - new Date().setDate(new Date().getDate() - 28), - new Date().setDate(new Date().getDate() - 29), - new Date().setDate(new Date().getDate() - 30), - new Date().setMonth(new Date().getMonth() - 1), - new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate( - new Date().getDate() - 20, - ), - new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate( - new Date().getDate() - 25, - ), - new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate( - new Date().getDate() - 28, - ), - new Date(new Date().setMonth(new Date().getMonth() - 1)).setDate( - new Date().getDate() - 29, - ), - new Date().setMonth(new Date().getMonth() - 11), - new Date(new Date().setMonth(new Date().getMonth() - 11)).setDate( - new Date().getDate() - 20, - ), - new Date(new Date().setMonth(new Date().getMonth() - 11)).setDate( - new Date().getDate() - 25, - ), - oneYearDate, - ] - const outputs = [ - new Date(1671461038).toLocaleDateString(), - new Date('04 Dec 1995 00:12:00 GMT').toLocaleDateString(), - 'now', - '10s', - '10m', - '1h', - '1d', - '20d', - '25d', - '28d', - '29d', - '1mo', - '1mo', - '1mo', - '1mo', - '2mo', - '2mo', - '11mo', - '11mo', - '11mo', - new Date(oneYearDate).toLocaleDateString(), +describe('dateDiff', () => { + type Case = [number | string | Date, number | string | Date, string] + const cases: Case[] = [ + [ + 1671461038, + new Date('2034-05-31T22:00Z'), + new Date(1671461038).toLocaleDateString(), + ], + [ + '04 Dec 1995 00:12:00 GMT', + new Date('2024-05-31T22:00Z'), + new Date('04 Dec 1995 00:12:00 GMT').toLocaleDateString(), + ], + [new Date('2010-01-01Z'), new Date('2005-01-01Z'), 'now'], + [new Date(), new Date(), 'now'], + [ + new Date('2001-01-01T00:00:00Z'), + new Date('2001-01-01T00:00:04.999Z'), + 'now', + ], + [new Date('2001-01-01T12:00:00Z'), new Date('2001-01-01T12:00:05Z'), '5s'], + [ + new Date('2001-01-01T21:59:55Z'), + new Date('2001-01-01T22:00:05.123Z'), + '10s', + ], + [new Date('2001-01-01Z'), new Date('2001-01-01T00:00:59.999Z'), '59s'], + [new Date('2001-01-01Z'), new Date('2001-01-01T00:01:00.000Z'), '1m'], + [new Date('2001-01-01T23:59:50Z'), new Date('2001-01-02T00:09:55Z'), '10m'], + [ + new Date('2001-01-01T23:00:00Z'), + new Date('2001-01-01T23:59:59.999Z'), + '59m', + ], + [new Date('2001-01-01T23:00:00Z'), new Date('2001-01-02T00:00:00Z'), '1h'], + [new Date('2001-01-01Z'), new Date('2001-01-01T23:59:59.999Z'), '23h'], + [new Date('2001-01-01Z'), new Date('2001-01-02Z'), '1d'], + [new Date('2001-01-01T14:00Z'), new Date('2001-01-02T14:00Z'), '1d'], + [new Date('2001-02-25Z'), new Date('2001-03-03Z'), '6d'], + [new Date('2004-02-25Z'), new Date('2004-03-03Z'), '7d'], + [new Date('2001-01-01Z'), new Date('2001-01-30T00:00Z'), '29d'], + [new Date('2004-01-01Z'), new Date('2004-01-30T23:59:59.999Z'), '29d'], + [new Date('2001-01-01Z'), new Date('2001-01-31T00:00Z'), '1mo'], + [new Date('2001-02-01Z'), new Date('2001-03-01Z'), '28d'], + [new Date('2001-01-01Z'), new Date('2001-01-31Z'), '1mo'], + [new Date('2001-02-01Z'), new Date('2001-03-01Z'), '28d'], + [new Date('2004-02-01Z'), new Date('2004-03-01Z'), '29d'], + [new Date('2001-03-01Z'), new Date('2001-04-01Z'), '1mo'], + [new Date('2001-01-01Z'), new Date('2001-03-01Z'), '1mo'], // 31 + 28 = 59 days -> 1 months + [new Date('2001-01-01Z'), new Date('2001-04-01Z'), '3mo'], // 31 + 28 + 31 = 90 days -> 3 months + [new Date('2001-02-01Z'), new Date('2001-05-01Z'), '2mo'], // 28 + 31 + 30 = 89 days -> 2 months + [new Date('2001-01-01Z'), new Date('2001-05-01Z'), '4mo'], // 31 + 28 + 31 + 30 = 120 days -> 4 months + [new Date('2001-02-01Z'), new Date('2001-06-01Z'), '4mo'], // 28 + 31 + 30 + 31 = 120 days -> 4 months + [new Date('2001-01-01Z'), new Date('2001-12-01Z'), '11mo'], + [new Date('2001-01-01Z'), new Date('2001-12-26Z'), '11mo'], + [new Date('2001-01-01Z'), new Date('2001-12-26T23:59:59.999Z'), '11mo'], + [ + new Date('2001-01-01Z'), + new Date('2001-12-27Z'), + new Date('2001-01-01Z').toLocaleDateString(), + ], + [ + new Date('2001-01-01Z'), + new Date('2002-01-01Z'), + new Date('2001-01-01Z').toLocaleDateString(), + ], + [ + new Date('2050-12-31Z'), + new Date('2100-01-01Z'), + new Date('2050-12-31Z').toLocaleDateString(), + ], ] - it('correctly calculates how much time passed, in a string', () => { - for (let i = 0; i < inputs.length; i++) { - const result = ago(inputs[i]) - expect(result).toEqual(outputs[i]) - } - }) + it.each(cases)( + 'given the dates %p and %p returns %p', + (date1, date2, expected) => { + const result = dateDiff(date1, date2) + expect(result).toEqual(expected) + }, + ) }) describe('makeValidHandle', () => { diff --git a/src/lib/strings/time.ts b/src/lib/strings/time.ts index 8de4b52aed..2d5291a7fd 100644 --- a/src/lib/strings/time.ts +++ b/src/lib/strings/time.ts @@ -1,19 +1,34 @@ +export function ago(date: number | string | Date): string { + return dateDiff(date, Date.now()) +} + const NOW = 5 const MINUTE = 60 const HOUR = MINUTE * 60 const DAY = HOUR * 24 const MONTH_30 = DAY * 30 -const MONTH = DAY * 30.41675 // This results in 365.001 days in a year, which is close enough for nearly all cases -export function ago(date: number | string | Date): string { - let ts: number - if (typeof date === 'string') { - ts = Number(new Date(date)) - } else if (date instanceof Date) { - ts = Number(date) +/** Returns the difference between date1 and date2 in full seconds, minutes, hours etc. All month are considered exactly 30 days. Assuming that date1 <= date2. Differences >= 360 days are returned as the "M/D/YYYY" string. */ +export function dateDiff( + date1: number | string | Date, + date2: number | string | Date, +): string { + let ts1: number + if (typeof date1 === 'string') { + ts1 = Number(new Date(date1)) + } else if (date1 instanceof Date) { + ts1 = Number(date1) } else { - ts = date + ts1 = date1 } - const diffSeconds = Math.floor((Date.now() - ts) / 1e3) + let ts2: number + if (typeof date2 === 'string') { + ts2 = Number(new Date(date2)) + } else if (date2 instanceof Date) { + ts2 = Number(date2) + } else { + ts2 = date2 + } + const diffSeconds = Math.floor((ts2 - ts1) / 1e3) if (diffSeconds < NOW) { return `now` } else if (diffSeconds < MINUTE) { @@ -23,19 +38,13 @@ export function ago(date: number | string | Date): string { } else if (diffSeconds < DAY) { return `${Math.floor(diffSeconds / HOUR)}h` } else if (diffSeconds < MONTH_30) { - return `${Math.round(diffSeconds / DAY)}d` + return `${Math.floor(diffSeconds / DAY)}d` } else { - let months = diffSeconds / MONTH - if (months % 1 >= 0.9) { - months = Math.ceil(months) + const diffMonths30 = Math.floor(diffSeconds / MONTH_30) + if (diffMonths30 < 12) { + return `${diffMonths30}mo` } else { - months = Math.floor(months) - } - - if (months < 12) { - return `${months}mo` - } else { - return new Date(ts).toLocaleDateString() + return new Date(ts1).toLocaleDateString() } } }