Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/calendar-web/src/timezone/CalendarTimeZoneHintSync.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>): Calendar {
return {
Expand Down Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -54,16 +56,13 @@ export const calendarIdsWithVisibleEventsOnDay = ({
"allDayEvents" | "dateInView" | "hiddenEventIds" | "timedEvents"
>): Set<CalendarId> => {
const ids = new Set<CalendarId>();
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;
}
Expand Down
Loading