fix: use UTC timestamps for timed event rendering
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
getEventDayBounds,
|
||||
getEventDisplayEndDate,
|
||||
getEventEndDate,
|
||||
getEventStartDate,
|
||||
getTimedEventBoundsForDay,
|
||||
isTimedEventFullDayOnDate,
|
||||
layoutOverlappingEvents,
|
||||
@@ -96,7 +97,21 @@ describe('calendar-utils all-day handling', () => {
|
||||
utcEnd: '2026-03-14T11:00:00Z',
|
||||
});
|
||||
|
||||
expectLocalDateParts(getEventDisplayEndDate(event), 2026, 3, 14, 11);
|
||||
expect(getEventDisplayEndDate(event).toISOString()).toBe('2026-03-14T11:00:00.000Z');
|
||||
});
|
||||
|
||||
it('prefers utc timestamps for timed events with an event timezone', () => {
|
||||
const event = makeEvent({
|
||||
start: '2026-03-15T09:00:00',
|
||||
duration: 'PT1H',
|
||||
timeZone: 'America/New_York',
|
||||
showWithoutTime: false,
|
||||
utcStart: '2026-03-15T13:00:00Z',
|
||||
utcEnd: '2026-03-15T14:00:00Z',
|
||||
});
|
||||
|
||||
expect(getEventStartDate(event).toISOString()).toBe('2026-03-15T13:00:00.000Z');
|
||||
expect(getEventEndDate(event).toISOString()).toBe('2026-03-15T14:00:00.000Z');
|
||||
});
|
||||
|
||||
it('clips timed multi-day events to the visible day bounds', () => {
|
||||
@@ -109,7 +124,7 @@ describe('calendar-utils all-day handling', () => {
|
||||
});
|
||||
|
||||
expect(getTimedEventBoundsForDay(event, new Date('2026-03-14T00:00:00Z'))).toMatchObject({
|
||||
startMinutes: 1320,
|
||||
startMinutes: 1380,
|
||||
endMinutes: 1440,
|
||||
continuesBefore: false,
|
||||
continuesAfter: true,
|
||||
@@ -117,7 +132,7 @@ describe('calendar-utils all-day handling', () => {
|
||||
|
||||
expect(getTimedEventBoundsForDay(event, new Date('2026-03-15T00:00:00Z'))).toMatchObject({
|
||||
startMinutes: 0,
|
||||
endMinutes: 120,
|
||||
endMinutes: 180,
|
||||
continuesBefore: true,
|
||||
continuesAfter: false,
|
||||
});
|
||||
@@ -137,7 +152,7 @@ describe('calendar-utils all-day handling', () => {
|
||||
expect(layout).toHaveLength(1);
|
||||
expect(layout[0]).toMatchObject({
|
||||
startMinutes: 0,
|
||||
endMinutes: 120,
|
||||
endMinutes: 180,
|
||||
column: 0,
|
||||
totalColumns: 1,
|
||||
continuesBefore: true,
|
||||
|
||||
+17
-5
@@ -21,6 +21,13 @@ export interface TimedEventLayout {
|
||||
continuesAfter: boolean;
|
||||
}
|
||||
|
||||
export function getEventStartDate(
|
||||
event: Pick<CalendarEvent, 'start' | 'utcStart' | 'showWithoutTime'>,
|
||||
): Date {
|
||||
const source = !event.showWithoutTime && event.utcStart ? event.utcStart : event.start;
|
||||
return parseISO(source);
|
||||
}
|
||||
|
||||
export function packWeekSegments(rawSegments: CalendarWeekSegment[]): CalendarWeekSegment[] {
|
||||
rawSegments.sort((left, right) => {
|
||||
if (left.startIndex !== right.startIndex) return left.startIndex - right.startIndex;
|
||||
@@ -28,7 +35,7 @@ export function packWeekSegments(rawSegments: CalendarWeekSegment[]): CalendarWe
|
||||
if (left.event.showWithoutTime !== right.event.showWithoutTime) {
|
||||
return left.event.showWithoutTime ? -1 : 1;
|
||||
}
|
||||
const timeDiff = parseISO(left.event.start).getTime() - parseISO(right.event.start).getTime();
|
||||
const timeDiff = getEventStartDate(left.event).getTime() - getEventStartDate(right.event).getTime();
|
||||
if (timeDiff !== 0) return timeDiff;
|
||||
return (left.event.title || "").localeCompare(right.event.title || "");
|
||||
});
|
||||
@@ -48,14 +55,19 @@ export function packWeekSegments(rawSegments: CalendarWeekSegment[]): CalendarWe
|
||||
}
|
||||
|
||||
export function getEventEndDate(event: CalendarEvent): Date {
|
||||
const start = parseISO(event.start);
|
||||
if (!event.showWithoutTime && event.utcEnd) {
|
||||
return parseISO(event.utcEnd);
|
||||
}
|
||||
|
||||
const start = getEventStartDate(event);
|
||||
if (!event.duration) return start;
|
||||
return new Date(start.getTime() + parseDuration(event.duration) * 60000);
|
||||
}
|
||||
|
||||
export function getEventDisplayEndDate(event: CalendarEvent): Date {
|
||||
const end = getEventEndDate(event);
|
||||
if (!event.showWithoutTime || end.getTime() <= parseISO(event.start).getTime()) {
|
||||
const start = getEventStartDate(event);
|
||||
if (!event.showWithoutTime || end.getTime() <= start.getTime()) {
|
||||
return end;
|
||||
}
|
||||
return subMilliseconds(end, 1);
|
||||
@@ -63,7 +75,7 @@ export function getEventDisplayEndDate(event: CalendarEvent): Date {
|
||||
|
||||
export function getEventDayBounds(event: CalendarEvent): { startDay: Date; endDay: Date } {
|
||||
return {
|
||||
startDay: startOfDay(parseISO(event.start)),
|
||||
startDay: startOfDay(getEventStartDate(event)),
|
||||
endDay: startOfDay(getEventDisplayEndDate(event)),
|
||||
};
|
||||
}
|
||||
@@ -74,7 +86,7 @@ export function getTimedEventBoundsForDay(
|
||||
): { startMinutes: number; endMinutes: number; continuesBefore: boolean; continuesAfter: boolean } | null {
|
||||
if (event.showWithoutTime) return null;
|
||||
|
||||
const eventStart = parseISO(event.start);
|
||||
const eventStart = getEventStartDate(event);
|
||||
const eventEnd = getEventEndDate(event);
|
||||
const dayStart = startOfDay(day);
|
||||
const nextDayStart = addDays(dayStart, 1);
|
||||
|
||||
Reference in New Issue
Block a user