diff --git a/apps/calendar-web/src/timezone/CalendarTimeZoneHintSync.tsx b/apps/calendar-web/src/timezone/CalendarTimeZoneHintSync.tsx index 99716fffe..89b3562eb 100644 --- a/apps/calendar-web/src/timezone/CalendarTimeZoneHintSync.tsx +++ b/apps/calendar-web/src/timezone/CalendarTimeZoneHintSync.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useLayoutEffect } from "react"; import { useCalendarsQuery } from "@web/calendars/calendar.query"; import { pickCalendarTimeZoneHint } from "@web/timezone/calendar-timezone-hint"; import { setCalendarTimeZoneHint } from "@web/timezone/effective-timezone.store"; @@ -7,7 +7,7 @@ import { setCalendarTimeZoneHint } from "@web/timezone/effective-timezone.store" export function CalendarTimeZoneHintSync() { const { data: calendars } = useCalendarsQuery(); - useEffect(() => { + useLayoutEffect(() => { setCalendarTimeZoneHint( calendars ? pickCalendarTimeZoneHint(calendars) : null, ); diff --git a/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.test.ts b/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.test.ts index 005530a73..93caeff47 100644 --- a/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.test.ts +++ b/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.test.ts @@ -6,11 +6,15 @@ import { import dayjs from "@core/util/date/dayjs"; import { type GridEvent } from "@web/common/types/web.event.types"; import { gridEventDefaultPosition } from "@web/common/utils/event/event.util"; +import { + resetEffectiveTimeZoneStoreForTests, + setBrowserTimeZoneForTests, +} from "@web/timezone/effective-timezone.store"; import { calendarIdsWithVisibleEventsOnDay, getDayViewCalendars, } from "./dayCalendarColumns.util"; -import { describe, expect, it } from "bun:test"; +import { afterEach, describe, expect, it } from "bun:test"; function makeCalendar(overrides: Partial): Calendar { return { @@ -208,6 +212,53 @@ describe("getDayViewCalendars", () => { }); describe("calendarIdsWithVisibleEventsOnDay", () => { + afterEach(() => { + resetEffectiveTimeZoneStoreForTests(); + }); + + it("keeps both day columns at the merge-queue UTC boundary (browser UTC, no calendar hint)", () => { + setBrowserTimeZoneForTests("UTC"); + const primary = makeCalendar({ + id: "507f1f77bcf86cd799439011" as Calendar["id"], + name: "Acme", + isPrimary: true, + }); + const secondary = makeCalendar({ + id: "507f1f77bcf86cd799439012" as Calendar["id"], + name: "Design", + }); + const dateInViewBoundary = dayjs.tz( + "2026-11-11T17:23:00", + "America/Denver", + ); + const timed = [ + gridEvent({ + _id: "a", + calendarId: primary.id, + startDate: "2026-11-11 09:00:00.000", + endDate: "2026-11-11 10:30:00.000", + timeZone: "America/Denver", + }), + gridEvent({ + _id: "b", + calendarId: secondary.id, + startDate: "2026-11-11 13:00:00.000", + endDate: "2026-11-11 14:30:00.000", + timeZone: "America/Denver", + }), + ]; + + expect( + getDayViewCalendars({ + allDayEvents: [], + calendars: [primary, secondary], + dateInView: dateInViewBoundary, + hiddenEventIds: new Set(), + timedEvents: timed, + }).map((calendar) => calendar.name), + ).toEqual(["Acme", "Design"]); + }); + it("returns an empty set when every event is hidden", () => { const timed = [ gridEvent({ diff --git a/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.ts b/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.ts index dcbb1f137..ac16a47ad 100644 --- a/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.ts +++ b/apps/calendar-web/src/views/Day/components/Calendar/dayCalendarColumns.util.ts @@ -1,11 +1,11 @@ import { type Calendar } from "@core/types/calendar.contracts"; import { type CalendarId } from "@core/types/domain-primitives"; -import { type Dayjs } from "@core/util/date/dayjs"; +import dayjs, { type Dayjs } from "@core/util/date/dayjs"; import { getDefaultTargetCalendar } from "@web/calendars/calendar.util"; import { type GridEvent } from "@web/common/types/web.event.types"; import { isEventVisibleOnGrid } from "@web/events/visibility/filter-events-visible-on-grid"; -import { getEffectiveTimeZone } from "@web/timezone/effective-timezone.store"; -import { inEffectiveTimeZone } from "@web/timezone/in-time-zone"; +import { parseGridScheduleDate } from "@web/timezone/parse-grid-schedule-date"; +import { dayEventQueryRange } from "@web/views/Day/util/day-window.util"; import { isAllDayEventOnDay } from "./dayAllDayRows.util"; type DayViewCalendarColumnInput = { @@ -37,12 +37,14 @@ const eligibleCalendarsInOrder = (calendars: Calendar[]): Calendar[] => { return calendars.slice(0, 1); }; -const CIVIL_DAY_FORMAT = "YYYY-MM-DD"; - -const timedEventCivilDay = (event: GridEvent, viewZone: string) => - inEffectiveTimeZone(event.startDate, event.timeZone ?? viewZone).format( - CIVIL_DAY_FORMAT, - ); +const timedEventOverlapsViewDay = (event: GridEvent, dateInView: Dayjs) => { + const { startDate, endDate } = dayEventQueryRange(dateInView); + const rangeStart = dayjs(startDate); + const rangeEnd = dayjs(endDate); + const eventStart = parseGridScheduleDate(event.startDate, event.timeZone); + const eventEnd = parseGridScheduleDate(event.endDate, event.timeZone); + return eventStart.isBefore(rangeEnd) && eventEnd.isAfter(rangeStart); +}; export const calendarIdsWithVisibleEventsOnDay = ({ allDayEvents, @@ -54,16 +56,13 @@ export const calendarIdsWithVisibleEventsOnDay = ({ "allDayEvents" | "dateInView" | "hiddenEventIds" | "timedEvents" >): Set => { const ids = new Set(); - const viewZone = getEffectiveTimeZone(); - const viewDay = inEffectiveTimeZone(dateInView, viewZone).format( - CIVIL_DAY_FORMAT, - ); for (const event of timedEvents) { if ( !event.calendarId || !isEventVisibleOnGrid(event, hiddenEventIds) || - timedEventCivilDay(event, viewZone) !== viewDay + event.isAllDay || + !timedEventOverlapsViewDay(event, dateInView) ) { continue; }