fix: show the scheduled time rounded to the minute and with its date

This commit is contained in:
Linus Rath
2026-10-11 19:50:40 +02:00
parent 8e074503a3
commit 2ae8c89990
4 changed files with 54 additions and 4 deletions
+2 -1
View File
@@ -16,6 +16,7 @@ import { useCopyLink } from "@/hooks/use-copy-link";
import { Button } from "@/components/ui/button";
import { Avatar } from "@/components/ui/avatar";
import { formatFileSize, cn, MailboxNode, formatDate, formatDateTime, generateUUID } from "@/lib/utils";
import { formatScheduledSendAt } from "@/lib/scheduled-send-label";
import { buildMoveTargets, resolveMoveOwnerAccountId } from "@/lib/move-targets";
import { emailDisplayDate } from "@/lib/email-date";
import { TagBadge } from "./tag-badge";
@@ -4722,7 +4723,7 @@ export function EmailViewer({
<div className="flex items-center gap-2 text-primary">
<CalendarClock className="w-4 h-4" />
<span className="text-sm font-medium">
{t('scheduled_banner', { date: email.scheduledSendAt ? formatDateTime(email.scheduledSendAt, timeFormat) : '' })}
{t('scheduled_banner', { date: email.scheduledSendAt ? formatScheduledSendAt(email.scheduledSendAt, timeFormat) : '' })}
</span>
</div>
<div className="flex flex-wrap items-center gap-2">
+4 -3
View File
@@ -1,7 +1,8 @@
"use client";
import React, { useCallback } from "react";
import { cleanPreview, formatDate, formatDateTime } from "@/lib/utils";
import { cleanPreview, formatDate } from "@/lib/utils";
import { formatScheduledSendAt } from "@/lib/scheduled-send-label";
import { Email, ThreadGroup } from "@/lib/jmap/types";
import { cn } from "@/lib/utils";
import { ListAttachmentChips, useListHasAttachment } from "./attachment-chips";
@@ -324,7 +325,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
const subjectSnippet = email.searchSnippet?.subject ?? null;
const previewSnippet = showPreview ? (email.searchSnippet?.preview ?? null) : null;
const scheduledSendLabel = email.isScheduled && email.scheduledSendAt
? formatDateTime(email.scheduledSendAt, timeFormat)
? formatScheduledSendAt(email.scheduledSendAt, timeFormat)
: null;
const tagIds = sortTagIds(getEmailTagIds(email.keywords));
@@ -825,7 +826,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
const subjectSnippet = threadSnippet?.subject ?? null;
const previewSnippet = showPreview ? (threadSnippet?.preview ?? null) : null;
const scheduledSendLabel = latestEmail.isScheduled && latestEmail.scheduledSendAt
? formatDateTime(latestEmail.scheduledSendAt, timeFormat)
? formatScheduledSendAt(latestEmail.scheduledSendAt, timeFormat)
: null;
const { selectedMailbox, mailboxes, selectedEmailKeys, toggleEmailSelection, selectRangeEmails, clearSelection, isUnifiedView, unifiedRole } = useEmailStore();
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest';
import { formatScheduledSendAt } from '../scheduled-send-label';
// Local times, so the expectations do not depend on the machine's time zone.
const at = (y: number, mo: number, d: number, h: number, mi: number, s = 0) => new Date(y, mo - 1, d, h, mi, s);
describe('formatScheduledSendAt', () => {
const today = at(2026, 10, 11, 18, 0);
it('shows 08:00 for a release one second before eight', () => {
const label = formatScheduledSendAt(at(2026, 10, 11, 19, 59, 59).toISOString(), '24h', today);
expect(label).toContain('20:00');
expect(label).not.toContain('19:59');
});
it('shows only the time for a release later today', () => {
expect(formatScheduledSendAt(at(2026, 10, 11, 21, 30).toISOString(), '24h', today)).toBe('21:30');
});
it('includes the date for a release on another day', () => {
const label = formatScheduledSendAt(at(2026, 10, 12, 7, 59, 59).toISOString(), '24h', today);
expect(label).toContain('08:00');
expect(label).toMatch(/12/);
});
});
+23
View File
@@ -0,0 +1,23 @@
import { formatDateTime } from '@/lib/utils';
/**
* The release time of a scheduled message, for lists and banners.
*
* The server can land a second before the minute the user picked (08:00
* showed as 07:59), so the time is rounded to the nearest minute. A message
* that is not due today also shows its date; a bare "07:59" for tomorrow
* morning read like a time that had already passed.
*/
export function formatScheduledSendAt(
sendAt: string,
timeFormat: '12h' | '24h',
now: Date = new Date(),
): string {
const ms = Date.parse(sendAt);
if (!Number.isFinite(ms)) return sendAt;
const rounded = new Date(Math.round(ms / 60_000) * 60_000);
const sameDay = rounded.toDateString() === now.toDateString();
return sameDay
? formatDateTime(rounded, timeFormat)
: formatDateTime(rounded, timeFormat, { weekday: 'short', month: 'short', day: 'numeric' });
}