Merge pull request #1262 from balabommablock-cpu/fix/relative-ago-calendar-days

fix: count calendar days in the time zone for the relative date
This commit is contained in:
Linus Rath
2026-10-18 14:34:14 +02:00
committed by GitHub
2 changed files with 29 additions and 2 deletions
+19 -1
View File
@@ -1,9 +1,15 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, afterEach } from 'vitest';
import { formatRelativeAgo } from '../utils';
import { AUTO_TIME_ZONE } from '../timezone';
import { useSettingsStore } from '@/stores/settings-store';
describe('formatRelativeAgo', () => {
const now = new Date('2026-07-01T12:00:00Z');
afterEach(() => {
useSettingsStore.setState({ timeZone: AUTO_TIME_ZONE });
});
it('says how long ago in the largest whole unit', () => {
expect(formatRelativeAgo(new Date('2026-07-01T11:57:00Z'), now)).toBe('3 minutes ago');
expect(formatRelativeAgo(new Date('2026-07-01T08:00:00Z'), now)).toBe('4 hours ago');
@@ -14,4 +20,16 @@ describe('formatRelativeAgo', () => {
expect(formatRelativeAgo(new Date('2026-05-01T12:00:00Z'), now)).toBe('');
expect(formatRelativeAgo('not a date', now)).toBe('');
});
it('counts calendar days in the effective time zone (#1259)', () => {
useSettingsStore.setState({ timeZone: 'America/Denver' });
// 16 Oct, 10:00 in Denver.
const denverNow = new Date('2026-10-16T16:00:00Z');
// 14 Oct, 20:51 in Denver: only 37 hours ago, but two days back.
expect(formatRelativeAgo(new Date('2026-10-15T02:51:30Z'), denverNow)).toBe('2 days ago');
// 15 Oct, 08:00 in Denver: 26 hours ago, and yesterday.
expect(formatRelativeAgo(new Date('2026-10-15T14:00:00Z'), denverNow)).toBe('yesterday');
// Under a day still reads in hours, even across midnight.
expect(formatRelativeAgo(new Date('2026-10-15T23:00:00Z'), denverNow)).toBe('17 hours ago');
});
});
+10 -1
View File
@@ -258,6 +258,10 @@ export function formatDateTime(
* How long ago `date` was, in words ("3 minutes ago", "2 days ago"), for the
* parenthesis Gmail puts after a message's time. Empty once it is a month
* old, where the date alone says enough.
*
* Days are calendar days in the user's effective time zone, so a mail from
* the day before yesterday is never called "yesterday" just because fewer
* than 48 hours have passed. (#1259)
*/
export function formatRelativeAgo(date: Date | string, now: Date = new Date()): string {
const d = typeof date === 'string' ? new Date(date) : date;
@@ -265,7 +269,12 @@ export function formatRelativeAgo(date: Date | string, now: Date = new Date()):
const diff = Math.max(0, now.getTime() - d.getTime());
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
const timeZone = getEffectiveTimeZone();
const dayNumber = (x: Date) => {
const w = getWallClock(x, timeZone);
return Date.UTC(w.year, w.month - 1, w.day) / 86400000;
};
const days = Math.max(1, dayNumber(now) - dayNumber(d));
if (days >= 30) return '';
const locale = getEffectiveLocale();
const rtf = new Intl.RelativeTimeFormat(locale === 'en' ? 'en-US' : locale, { numeric: 'auto' });