From a7a4378a40d17612f3b09049d059d2caa8c6c933 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:41:55 -0700 Subject: [PATCH] Fix: ensure correct relative dates formatter uses calendar days (#7174) --- next-i18next.config.js | 6 +++ src/utils/relative-date-formatter.test.js | 46 +++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/utils/relative-date-formatter.test.js diff --git a/next-i18next.config.js b/next-i18next.config.js index ac1eac369..5c3613b3a 100644 --- a/next-i18next.config.js +++ b/next-i18next.config.js @@ -106,6 +106,12 @@ function relativeDate(date, formatter) { const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(delta)); const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1; + if (units[unitIndex] === "day") { + // compare calendar days, not elapsed 24h blocks + const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()); + return formatter.format(Math.round((startOfDay(date) - startOfDay(new Date())) / 86400000), "day"); + } + return formatter.format(Math.floor(delta / divisor), units[unitIndex]); } diff --git a/src/utils/relative-date-formatter.test.js b/src/utils/relative-date-formatter.test.js new file mode 100644 index 000000000..baf1b0e69 --- /dev/null +++ b/src/utils/relative-date-formatter.test.js @@ -0,0 +1,46 @@ +import { createRequire } from "node:module"; + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const require = createRequire(import.meta.url); +const i18nextConfig = require("../../next-i18next.config.js"); + +function getRelativeDateFormatter() { + let relativeDateFormatter; + + i18nextConfig.use[0].init({ + services: { + formatter: { + add(name, formatter) { + if (name === "relativeDate") relativeDateFormatter = formatter; + }, + }, + }, + }); + + return relativeDateFormatter; +} + +describe("relativeDate formatter", () => { + const formatRelativeDate = getRelativeDateFormatter(); + const options = { numeric: "auto" }; + + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 8, 23, 16, 0)); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it.each([ + ["hours away on the same day", new Date(2026, 8, 23, 20, 0), "in 4 hours"], + ["next calendar day", new Date(2026, 8, 24, 20, 0), "tomorrow"], + ["two calendar days away but under 48h", new Date(2026, 8, 25, 10, 40), "in 2 days"], + ["previous calendar day", new Date(2026, 8, 22, 10, 0), "yesterday"], + ["two calendar days ago but under 48h", new Date(2026, 8, 21, 20, 0), "2 days ago"], + ])("formats %s", (_description, value, expected) => { + expect(formatRelativeDate(value.toISOString(), "en", options)).toBe(expected); + }); +});