From fd85788e2cfbfb7f09dabe34f595d09879d85980 Mon Sep 17 00:00:00 2001 From: Lily Foster Date: Fri, 31 May 2024 16:50:13 -0400 Subject: [PATCH] correctly count month durations relative to the end of longer months --- src/duration.ts | 14 ++++++++++---- test/duration.ts | 9 +++++++-- test/relative-time.js | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/duration.ts b/src/duration.ts index a0529e3..77de611 100644 --- a/src/duration.ts +++ b/src/duration.ts @@ -153,16 +153,23 @@ export function roundToSingleUnit(duration: Duration, {relativeTo = Date.now()}: // Resolve calendar dates const currentYear = relativeTo.getFullYear() - let currentMonth = relativeTo.getMonth() + const currentMonth = relativeTo.getMonth() const currentDate = relativeTo.getDate() if (days >= 27 || years + months + days) { + const newMonthDate = new Date(relativeTo) + newMonthDate.setDate(1) + newMonthDate.setMonth(currentMonth + months * sign + 1) + newMonthDate.setDate(0) + const monthDateCorrection = Math.max(0, currentDate - newMonthDate.getDate()) + const newDate = new Date(relativeTo) newDate.setFullYear(currentYear + years * sign) + newDate.setDate(currentDate - monthDateCorrection) newDate.setMonth(currentMonth + months * sign) - newDate.setDate(currentDate + days * sign) + newDate.setDate(currentDate - monthDateCorrection + days * sign) const yearDiff = newDate.getFullYear() - relativeTo.getFullYear() const monthDiff = newDate.getMonth() - relativeTo.getMonth() - const daysDiff = Math.abs(Math.round((Number(newDate) - Number(relativeTo)) / 86400000)) + const daysDiff = Math.abs(Math.round((Number(newDate) - Number(relativeTo)) / 86400000)) + monthDateCorrection const monthsDiff = Math.abs(yearDiff * 12 + monthDiff) if (daysDiff < 27) { if (days >= 6) { @@ -180,7 +187,6 @@ export function roundToSingleUnit(duration: Duration, {relativeTo = Date.now()}: years = yearDiff * sign } if (months || years) days = 0 - currentMonth = relativeTo.getMonth() } if (years) months = 0 diff --git a/test/duration.ts b/test/duration.ts index d90c490..03d2177 100644 --- a/test/duration.ts +++ b/test/duration.ts @@ -367,6 +367,9 @@ suite('duration', function () { ['-P55D', [-1, 'month'], {relativeTo: '2023-02-27T22:22:57Z'}], ['-P65D', [-3, 'month'], {relativeTo: '2023-02-28T22:22:57Z'}], ['-P75D', [-3, 'month'], {relativeTo: '2023-03-09T22:22:57Z'}], + ['P1M', [1, 'month'], {relativeTo: '2024-05-31T00:00:00Z'}], + ['-P1M', [-1, 'month'], {relativeTo: '2024-05-31T00:00:00Z'}], + ['-P3M', [-3, 'month'], {relativeTo: '2023-05-30T00:00:00Z'}], [ 'P8M', [8, 'month'], @@ -396,6 +399,8 @@ suite('duration', function () { }, ], ['P1M1D', [1, 'month'], {relativeTo: new Date('2022-12-01T00:00:00Z')}], + ['P1M1D', [2, 'month'], {relativeTo: new Date('2023-01-31T00:00:00Z')}], + ['P1M30D', [2, 'month'], {relativeTo: new Date('2023-01-31T00:00:00Z')}], [ 'P9M20DT25H', [9, 'month'], @@ -478,14 +483,14 @@ suite('duration', function () { ], ]) for (const [input, [val, unit], opts] of relativeTests) { - test(`getRelativeTimeUnit(${input}) === [${val}, ${unit}]`, () => { + test(`getRelativeTimeUnit(${input}${opts ? `, ${JSON.stringify(opts)}` : ''}) === [${val}, ${unit}]`, () => { assert.deepEqual( getRelativeTimeUnit(Duration.from(input), opts || {relativeTo: new Date('2023-07-01T00:00:00')}), [val, unit], ) }) if (opts?.relativeTo) continue - test(`getRelativeTimeUnit(-${input}) === [-${val}, ${unit}]`, () => { + test(`getRelativeTimeUnit(-${input}${opts ? `, ${JSON.stringify(opts)}` : ''}) === [-${val}, ${unit}]`, () => { assert.deepEqual( getRelativeTimeUnit(Duration.from(`-${input}`), opts || {relativeTo: new Date('2023-07-01T00:00:00')}), [-val, unit], diff --git a/test/relative-time.js b/test/relative-time.js index bd3cb32..e139bee 100644 --- a/test/relative-time.js +++ b/test/relative-time.js @@ -482,6 +482,24 @@ suite('relative-time', function () { assert.equal(time.shadowRoot.textContent, '4 months ago') }) + test('rewrites from last few days of month to smaller last month', async () => { + freezeTime(new Date(2024, 4, 31)) + const time = document.createElement('relative-time') + time.setAttribute('tense', 'past') + time.setAttribute('datetime', '2024-04-30T00:00:00Z') + await Promise.resolve() + assert.equal(time.shadowRoot.textContent, 'last month') + }) + + test('rewrites from last few days of month to smaller previous month', async () => { + freezeTime(new Date(2024, 4, 31)) + const time = document.createElement('relative-time') + time.setAttribute('tense', 'past') + time.setAttribute('datetime', '2024-02-29T00:00:00Z') + await Promise.resolve() + assert.equal(time.shadowRoot.textContent, '3 months ago') + }) + test('micro formats years', async () => { const datetime = new Date() datetime.setFullYear(datetime.getFullYear() - 10)