From 3a67f6c21886f09e244c857b0dacf992ee26610b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 01:15:27 +0000 Subject: [PATCH 1/3] Initial plan From 956f8bc412296f1b50ccf5044dc6d825fec318bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 01:19:02 +0000 Subject: [PATCH 2/3] Display event notification times in Eastern time (America/New_York) Agent-Logs-Url: https://github.com/dev-chat/mocker/sessions/95f019eb-313a-44a1-95a8-046faa393de6 --- packages/backend/src/jobs/event-alert.job.spec.ts | 3 +++ packages/backend/src/jobs/event-alert.job.ts | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/jobs/event-alert.job.spec.ts b/packages/backend/src/jobs/event-alert.job.spec.ts index 0eb3e07c..182ab7d7 100644 --- a/packages/backend/src/jobs/event-alert.job.spec.ts +++ b/packages/backend/src/jobs/event-alert.job.spec.ts @@ -60,6 +60,9 @@ describe('EventAlertJob', () => { expect(sendMessageMock).toHaveBeenCalledOnce(); expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('Planning πŸš€ @ HQ')); + // Times should be formatted in Eastern time (EDT in April = UTC-4: 16:00 UTC β†’ 12:00 PM, 17:00 UTC β†’ 1:00 PM) + expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('12:00 PM')); + expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('1:00 PM EDT')); expect(setValueWithExpireMock).toHaveBeenCalledOnce(); }); diff --git a/packages/backend/src/jobs/event-alert.job.ts b/packages/backend/src/jobs/event-alert.job.ts index 3db8d6bd..344ed5ac 100644 --- a/packages/backend/src/jobs/event-alert.job.ts +++ b/packages/backend/src/jobs/event-alert.job.ts @@ -7,12 +7,13 @@ import { WebService } from '../shared/services/web/web.service'; const ALERT_CHANNEL = process.env.EVENTS_ALERT_CHANNEL ?? '#events'; const ALERT_LOOKAHEAD_MS = 24 * 60 * 60 * 1000; const ALERT_DEDUPE_TTL_MS = 26 * 60 * 60 * 1000; +const DISPLAY_TIMEZONE = 'America/New_York'; -const formatDateLabel = (value: Date, timeZone?: 'UTC'): string => +const formatDateLabel = (value: Date, timeZone: string = DISPLAY_TIMEZONE): string => value.toLocaleDateString('en-US', { month: 'short', day: 'numeric', - ...(timeZone ? { timeZone } : {}), + timeZone, }); const formatTimeLabel = (value: Date, includeTimeZoneName = false): string => @@ -20,6 +21,7 @@ const formatTimeLabel = (value: Date, includeTimeZoneName = false): string => hour: 'numeric', minute: '2-digit', hour12: true, + timeZone: DISPLAY_TIMEZONE, ...(includeTimeZoneName ? { timeZoneName: 'short' } : {}), }); From 9e7e1149e90f96083929bc4635fd950fd8d02cf7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 14:06:00 +0000 Subject: [PATCH 3/3] Use Slack date formatting for per-user timezone rendering in event alerts Agent-Logs-Url: https://github.com/dev-chat/mocker/sessions/b1638d81-578b-4d9f-ae41-ee7033a3f450 --- .../backend/src/jobs/event-alert.job.spec.ts | 17 +++++- packages/backend/src/jobs/event-alert.job.ts | 56 ++++++++++--------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/packages/backend/src/jobs/event-alert.job.spec.ts b/packages/backend/src/jobs/event-alert.job.spec.ts index 182ab7d7..975514cd 100644 --- a/packages/backend/src/jobs/event-alert.job.spec.ts +++ b/packages/backend/src/jobs/event-alert.job.spec.ts @@ -60,9 +60,20 @@ describe('EventAlertJob', () => { expect(sendMessageMock).toHaveBeenCalledOnce(); expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('Planning πŸš€ @ HQ')); - // Times should be formatted in Eastern time (EDT in April = UTC-4: 16:00 UTC β†’ 12:00 PM, 17:00 UTC β†’ 1:00 PM) - expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('12:00 PM')); - expect(sendMessageMock).toHaveBeenCalledWith('#events', expect.stringContaining('1:00 PM EDT')); + // Times should use Slack's format so each user sees them in their own Slack timezone. + // Unix seconds: start=1776787200 (2026-04-21T16:00Z), end=1776790800 (2026-04-21T17:00Z) + expect(sendMessageMock).toHaveBeenCalledWith( + '#events', + expect.stringContaining(''), + ); + expect(sendMessageMock).toHaveBeenCalledWith( + '#events', + expect.stringContaining(''), + ); + expect(sendMessageMock).toHaveBeenCalledWith( + '#events', + expect.stringContaining(''), + ); expect(setValueWithExpireMock).toHaveBeenCalledOnce(); }); diff --git a/packages/backend/src/jobs/event-alert.job.ts b/packages/backend/src/jobs/event-alert.job.ts index 344ed5ac..d35bece3 100644 --- a/packages/backend/src/jobs/event-alert.job.ts +++ b/packages/backend/src/jobs/event-alert.job.ts @@ -7,23 +7,20 @@ import { WebService } from '../shared/services/web/web.service'; const ALERT_CHANNEL = process.env.EVENTS_ALERT_CHANNEL ?? '#events'; const ALERT_LOOKAHEAD_MS = 24 * 60 * 60 * 1000; const ALERT_DEDUPE_TTL_MS = 26 * 60 * 60 * 1000; -const DISPLAY_TIMEZONE = 'America/New_York'; - -const formatDateLabel = (value: Date, timeZone: string = DISPLAY_TIMEZONE): string => - value.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - timeZone, - }); - -const formatTimeLabel = (value: Date, includeTimeZoneName = false): string => - value.toLocaleTimeString('en-US', { - hour: 'numeric', - minute: '2-digit', - hour12: true, - timeZone: DISPLAY_TIMEZONE, - ...(includeTimeZoneName ? { timeZoneName: 'short' } : {}), - }); + +/** Format a Date as "Mon Day" (e.g. "Apr 21") in UTC, used for all-day event labels. */ +const formatUtcDateLabel = (value: Date): string => + value.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' }); + +/** Format a Date as a 12-hour time string in UTC, used as Slack timestamp fallback text. */ +const formatUtcTimeLabel = (value: Date): string => + value.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: 'UTC' }); + +const toUnixSeconds = (value: Date): number => Math.floor(value.getTime() / 1000); + +/** Wrap a Date in Slack's syntax so each recipient sees it in their own Slack timezone. */ +const slackTimestamp = (value: Date, format: string, fallback: string): string => + ``; const subtractOneDayUtc = (value: Date): Date => { const next = new Date(value); @@ -33,23 +30,32 @@ const subtractOneDayUtc = (value: Date): Date => { const formatOccurrenceWindow = (startsAt: string, endsAt: string, isAllDay: boolean): string => { if (isAllDay) { + // All-day events are stored as midnight UTC boundaries; use plain UTC date labels. const start = new Date(startsAt); const inclusiveEnd = subtractOneDayUtc(new Date(endsAt)); - const startLabel = formatDateLabel(start, 'UTC'); - const endLabel = formatDateLabel(inclusiveEnd, 'UTC'); + const startLabel = formatUtcDateLabel(start); + const endLabel = formatUtcDateLabel(inclusiveEnd); return startLabel === endLabel ? `${startLabel} (all day)` : `${startLabel} - ${endLabel} (all day)`; } const start = new Date(startsAt); const end = new Date(endsAt); - const startDateLabel = formatDateLabel(start); - const endDateLabel = formatDateLabel(end); - - if (startDateLabel === endDateLabel) { - return `${startDateLabel}, ${formatTimeLabel(start)} - ${formatTimeLabel(end, true)}`; + // Compare UTC calendar days to decide whether to show the date once or for both endpoints. + const startUtcDate = formatUtcDateLabel(start); + const endUtcDate = formatUtcDateLabel(end); + + if (startUtcDate === endUtcDate) { + // Same UTC calendar day: show the date once, then the start–end time range. + const datePart = slackTimestamp(start, '{date_short}', startUtcDate); + const startTime = slackTimestamp(start, '{time}', formatUtcTimeLabel(start)); + const endTime = slackTimestamp(end, '{time}', formatUtcTimeLabel(end)); + return `${datePart}, ${startTime} - ${endTime}`; } - return `${startDateLabel}, ${formatTimeLabel(start, true)} - ${endDateLabel}, ${formatTimeLabel(end, true)}`; + // Different UTC calendar days: include date and time for both endpoints. + const startFallback = `${startUtcDate} at ${formatUtcTimeLabel(start)}`; + const endFallback = `${endUtcDate} at ${formatUtcTimeLabel(end)}`; + return `${slackTimestamp(start, '{date_short} at {time}', startFallback)} - ${slackTimestamp(end, '{date_short} at {time}', endFallback)}`; }; export class EventAlertJob {