fix: use UTC timestamps for timed event rendering
This commit is contained in:
@@ -39,6 +39,7 @@ import { InlineAppView } from "@/components/layout/inline-app-view";
|
|||||||
import { useSidebarApps } from "@/hooks/use-sidebar-apps";
|
import { useSidebarApps } from "@/hooks/use-sidebar-apps";
|
||||||
import { ResizeHandle } from "@/components/layout/resize-handle";
|
import { ResizeHandle } from "@/components/layout/resize-handle";
|
||||||
import { sanitizeOutgoingCalendarEventData } from "@/lib/calendar-event-normalization";
|
import { sanitizeOutgoingCalendarEventData } from "@/lib/calendar-event-normalization";
|
||||||
|
import { getEventStartDate } from "@/lib/calendar-utils";
|
||||||
import { useTaskStore } from "@/stores/task-store";
|
import { useTaskStore } from "@/stores/task-store";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { CalendarEvent, CalendarParticipant } from "@/lib/jmap/types";
|
import type { CalendarEvent, CalendarParticipant } from "@/lib/jmap/types";
|
||||||
@@ -385,12 +386,16 @@ export default function CalendarPage() {
|
|||||||
}
|
}
|
||||||
}, [client, fetchEvents]);
|
}, [client, fetchEvents]);
|
||||||
|
|
||||||
const focusCalendarOnEvent = useCallback((event: Pick<CalendarEvent, "start">) => {
|
const focusCalendarOnEvent = useCallback((event: Pick<Partial<CalendarEvent>, "start" | "utcStart" | "showWithoutTime">) => {
|
||||||
if (!event.start) {
|
if (!event.start) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventDate = parseISO(event.start);
|
const eventDate = getEventStartDate({
|
||||||
|
start: event.start,
|
||||||
|
utcStart: event.utcStart ?? null,
|
||||||
|
showWithoutTime: event.showWithoutTime ?? false,
|
||||||
|
});
|
||||||
if (Number.isNaN(eventDate.getTime())) {
|
if (Number.isNaN(eventDate.getTime())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import { useMemo, useRef, useEffect, useCallback } from "react";
|
import { useMemo, useRef, useEffect, useCallback } from "react";
|
||||||
import { useTranslations, useFormatter } from "next-intl";
|
import { useTranslations, useFormatter } from "next-intl";
|
||||||
import { format, parseISO, isToday, isTomorrow, startOfDay } from "date-fns";
|
import { format, isToday, isTomorrow, startOfDay } from "date-fns";
|
||||||
import { MapPin, Users } from "lucide-react";
|
import { MapPin, Users } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { parseDuration, getEventColor } from "./event-card";
|
import { parseDuration, getEventColor } from "./event-card";
|
||||||
import { getEventDayBounds, getPrimaryCalendarId } from "@/lib/calendar-utils";
|
import { getEventDayBounds, getEventEndDate, getEventStartDate, getPrimaryCalendarId } from "@/lib/calendar-utils";
|
||||||
import { getParticipantCount } from "@/lib/calendar-participants";
|
import { getParticipantCount } from "@/lib/calendar-participants";
|
||||||
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export function CalendarAgendaView({
|
|||||||
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const sorted = [...events].sort((a, b) =>
|
const sorted = [...events].sort((a, b) =>
|
||||||
parseISO(a.start).getTime() - parseISO(b.start).getTime()
|
getEventStartDate(a).getTime() - getEventStartDate(b).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
const groups: DayGroup[] = [];
|
const groups: DayGroup[] = [];
|
||||||
@@ -144,9 +144,9 @@ export function CalendarAgendaView({
|
|||||||
const calId = getPrimaryCalendarId(ev);
|
const calId = getPrimaryCalendarId(ev);
|
||||||
const calendar = calId ? calendarMap.get(calId) : undefined;
|
const calendar = calId ? calendarMap.get(calId) : undefined;
|
||||||
const color = getEventColor(ev, calendar);
|
const color = getEventColor(ev, calendar);
|
||||||
const start = parseISO(ev.start);
|
const start = getEventStartDate(ev);
|
||||||
const durMin = parseDuration(ev.duration);
|
const durMin = parseDuration(ev.duration);
|
||||||
const end = new Date(start.getTime() + durMin * 60000);
|
const end = getEventEndDate(ev);
|
||||||
const locationName = ev.locations
|
const locationName = ev.locations
|
||||||
? Object.values(ev.locations)[0]?.name
|
? Object.values(ev.locations)[0]?.name
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { useCallback, useState, type CSSProperties, type DragEvent } from "react
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
||||||
import { format, parseISO } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Users } from "lucide-react";
|
import { Users } from "lucide-react";
|
||||||
import { getParticipantCount } from "@/lib/calendar-participants";
|
import { getParticipantCount } from "@/lib/calendar-participants";
|
||||||
|
import { getEventEndDate, getEventStartDate } from "@/lib/calendar-utils";
|
||||||
import { useSettingsStore } from "@/stores/settings-store";
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
|
|
||||||
interface EventCardProps {
|
interface EventCardProps {
|
||||||
@@ -68,14 +69,14 @@ export function EventCard({ event, calendar, variant, onClick, onMouseEnter, onM
|
|||||||
const t = useTranslations("calendar");
|
const t = useTranslations("calendar");
|
||||||
const [isBeingDragged, setIsBeingDragged] = useState(false);
|
const [isBeingDragged, setIsBeingDragged] = useState(false);
|
||||||
const color = getEventColor(event, calendar);
|
const color = getEventColor(event, calendar);
|
||||||
const startDate = parseISO(event.start);
|
const startDate = getEventStartDate(event);
|
||||||
const timeFormat = useSettingsStore((state) => state.timeFormat);
|
const timeFormat = useSettingsStore((state) => state.timeFormat);
|
||||||
const showTimeInMonthView = useSettingsStore((state) => state.showTimeInMonthView);
|
const showTimeInMonthView = useSettingsStore((state) => state.showTimeInMonthView);
|
||||||
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
|
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
|
||||||
|
|
||||||
const calendarName = calendar?.name || "";
|
const calendarName = calendar?.name || "";
|
||||||
const durationMinutes = parseDuration(event.duration);
|
const durationMinutes = parseDuration(event.duration);
|
||||||
const endTime = new Date(startDate.getTime() + durationMinutes * 60000);
|
const endTime = getEventEndDate(event);
|
||||||
const timeString = `${format(startDate, timeFmt)} – ${format(endTime, timeFmt)}`;
|
const timeString = `${format(startDate, timeFmt)} – ${format(endTime, timeFmt)}`;
|
||||||
const ariaLabel = `${event.title || t("events.no_title")}, ${timeString}${calendarName ? `, ${calendarName}` : ""}`;
|
const ariaLabel = `${event.title || t("events.no_title")}, ${timeString}${calendarName ? `, ${calendarName}` : ""}`;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { format, parseISO } from "date-fns";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { CalendarEvent, Calendar, CalendarParticipant } from "@/lib/jmap/types";
|
import type { CalendarEvent, Calendar, CalendarParticipant } from "@/lib/jmap/types";
|
||||||
import { parseDuration, getEventColor } from "./event-card";
|
import { parseDuration, getEventColor } from "./event-card";
|
||||||
|
import { getEventEndDate, getEventStartDate } from "@/lib/calendar-utils";
|
||||||
import {
|
import {
|
||||||
isOrganizer,
|
isOrganizer,
|
||||||
getUserParticipantId,
|
getUserParticipantId,
|
||||||
@@ -138,9 +139,9 @@ export function EventDetailPopover({
|
|||||||
const [isSavingNote, setIsSavingNote] = useState(false);
|
const [isSavingNote, setIsSavingNote] = useState(false);
|
||||||
|
|
||||||
const color = getEventColor(event, calendar);
|
const color = getEventColor(event, calendar);
|
||||||
const startDate = parseISO(event.start);
|
const startDate = getEventStartDate(event);
|
||||||
const durationMinutes = parseDuration(event.duration);
|
const durationMinutes = parseDuration(event.duration);
|
||||||
const endDate = new Date(startDate.getTime() + durationMinutes * 60000);
|
const endDate = getEventEndDate(event);
|
||||||
|
|
||||||
const locationName = useMemo(() => {
|
const locationName = useMemo(() => {
|
||||||
if (!event.locations) return null;
|
if (!event.locations) return null;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { X, Trash2, Check, Users, CalendarDays, Copy, Pencil, Clock, MapPin, Vid
|
|||||||
import { format, parseISO, addHours, addDays } from "date-fns";
|
import { format, parseISO, addHours, addDays } from "date-fns";
|
||||||
import type { CalendarEvent, Calendar, CalendarParticipant } from "@/lib/jmap/types";
|
import type { CalendarEvent, Calendar, CalendarParticipant } from "@/lib/jmap/types";
|
||||||
import { parseDuration, getEventColor } from "./event-card";
|
import { parseDuration, getEventColor } from "./event-card";
|
||||||
import { buildAllDayDuration, getEventDisplayEndDate, getPrimaryCalendarId } from "@/lib/calendar-utils";
|
import { buildAllDayDuration, getEventDisplayEndDate, getEventEndDate, getEventStartDate, getPrimaryCalendarId } from "@/lib/calendar-utils";
|
||||||
import { ParticipantInput } from "./participant-input";
|
import { ParticipantInput } from "./participant-input";
|
||||||
import {
|
import {
|
||||||
isOrganizer,
|
isOrganizer,
|
||||||
@@ -159,7 +159,7 @@ export function EventModal({
|
|||||||
}, [event, existingParticipants]);
|
}, [event, existingParticipants]);
|
||||||
|
|
||||||
const getInitialStart = (): Date => {
|
const getInitialStart = (): Date => {
|
||||||
if (event?.start) return parseISO(event.start);
|
if (event?.start) return getEventStartDate(event);
|
||||||
if (defaultDate) {
|
if (defaultDate) {
|
||||||
const d = new Date(defaultDate);
|
const d = new Date(defaultDate);
|
||||||
if (defaultEndDate) return d;
|
if (defaultEndDate) return d;
|
||||||
@@ -177,9 +177,7 @@ export function EventModal({
|
|||||||
if (event.showWithoutTime) {
|
if (event.showWithoutTime) {
|
||||||
return getEventDisplayEndDate(event);
|
return getEventDisplayEndDate(event);
|
||||||
}
|
}
|
||||||
const s = parseISO(event.start);
|
return getEventEndDate(event);
|
||||||
const dur = parseDuration(event.duration);
|
|
||||||
return new Date(s.getTime() + dur * 60000);
|
|
||||||
}
|
}
|
||||||
if (defaultEndDate) return new Date(defaultEndDate);
|
if (defaultEndDate) return new Date(defaultEndDate);
|
||||||
return addHours(getInitialStart(), 1);
|
return addHours(getInitialStart(), 1);
|
||||||
@@ -384,7 +382,7 @@ export function EventModal({
|
|||||||
|
|
||||||
const handleDuplicate = useCallback(() => {
|
const handleDuplicate = useCallback(() => {
|
||||||
if (!event || !onDuplicate) return;
|
if (!event || !onDuplicate) return;
|
||||||
const start = parseISO(event.start);
|
const start = getEventStartDate(event);
|
||||||
const newStart = addDays(start, 1);
|
const newStart = addDays(start, 1);
|
||||||
const newUid = typeof crypto !== 'undefined' && crypto.randomUUID
|
const newUid = typeof crypto !== 'undefined' && crypto.randomUUID
|
||||||
? crypto.randomUUID()
|
? crypto.randomUUID()
|
||||||
@@ -456,9 +454,9 @@ export function EventModal({
|
|||||||
const hasParticipants = attendees.length > 0 || (event?.participants && Object.keys(event.participants).length > 0);
|
const hasParticipants = attendees.length > 0 || (event?.participants && Object.keys(event.participants).length > 0);
|
||||||
|
|
||||||
if (isAttendeeMode && event) {
|
if (isAttendeeMode && event) {
|
||||||
const startD = parseISO(event.start);
|
const startD = getEventStartDate(event);
|
||||||
const durMin = parseDuration(event.duration);
|
const durMin = parseDuration(event.duration);
|
||||||
const endD = new Date(startD.getTime() + durMin * 60000);
|
const endD = getEventEndDate(event);
|
||||||
const locationName = event.locations ? Object.values(event.locations)[0]?.name : null;
|
const locationName = event.locations ? Object.values(event.locations)[0]?.name : null;
|
||||||
const participants = getParticipantList(event);
|
const participants = getParticipantList(event);
|
||||||
|
|
||||||
@@ -567,9 +565,9 @@ export function EventModal({
|
|||||||
|
|
||||||
// View mode: read-only display of event details with Edit button
|
// View mode: read-only display of event details with Edit button
|
||||||
if (mode === "view" && event) {
|
if (mode === "view" && event) {
|
||||||
const startD = parseISO(event.start);
|
const startD = getEventStartDate(event);
|
||||||
const durMin = parseDuration(event.duration);
|
const durMin = parseDuration(event.duration);
|
||||||
const endD = new Date(startD.getTime() + durMin * 60000);
|
const endD = getEventEndDate(event);
|
||||||
const locationName = event.locations ? Object.values(event.locations)[0]?.name || null : null;
|
const locationName = event.locations ? Object.values(event.locations)[0]?.name || null : null;
|
||||||
const virtualLoc = event.virtualLocations ? Object.values(event.virtualLocations)[0]?.uri || null : null;
|
const virtualLoc = event.virtualLocations ? Object.values(event.virtualLocations)[0]?.uri || null : null;
|
||||||
const viewParticipants = getParticipantList(event);
|
const viewParticipants = getParticipantList(event);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { X, Upload, Check, Loader2, RefreshCw, Globe } from "lucide-react";
|
|||||||
import { format, parseISO } from "date-fns";
|
import { format, parseISO } from "date-fns";
|
||||||
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
import type { CalendarEvent, Calendar } from "@/lib/jmap/types";
|
||||||
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
||||||
|
import { getEventStartDate } from "@/lib/calendar-utils";
|
||||||
import { useCalendarStore } from "@/stores/calendar-store";
|
import { useCalendarStore } from "@/stores/calendar-store";
|
||||||
import { useSettingsStore } from "@/stores/settings-store";
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
import { toast } from "@/stores/toast-store";
|
import { toast } from "@/stores/toast-store";
|
||||||
@@ -195,7 +196,7 @@ export function ICalImportModal({ calendars, client, onClose }: ICalImportModalP
|
|||||||
const formatEventDate = (event: Partial<CalendarEvent>): string => {
|
const formatEventDate = (event: Partial<CalendarEvent>): string => {
|
||||||
if (!event.start) return "";
|
if (!event.start) return "";
|
||||||
try {
|
try {
|
||||||
const date = parseISO(event.start);
|
const date = getEventStartDate(event as CalendarEvent);
|
||||||
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
|
const timeFmt = timeFormat === "12h" ? "h:mm a" : "HH:mm";
|
||||||
return event.showWithoutTime
|
return event.showWithoutTime
|
||||||
? format(date, "MMM d, yyyy")
|
? format(date, "MMM d, yyyy")
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import {
|
|||||||
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
||||||
addMonths, subMonths, addYears, subYears, setMonth, setYear,
|
addMonths, subMonths, addYears, subYears, setMonth, setYear,
|
||||||
eachDayOfInterval, getMonth, getYear, getISOWeek, getWeek,
|
eachDayOfInterval, getMonth, getYear, getISOWeek, getWeek,
|
||||||
isSameDay, isSameMonth, isToday, format, parseISO,
|
isSameDay, isSameMonth, isToday, format,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { getEventDayBounds } from "@/lib/calendar-utils";
|
||||||
import type { CalendarEvent } from "@/lib/jmap/types";
|
import type { CalendarEvent } from "@/lib/jmap/types";
|
||||||
|
|
||||||
type PickerView = "days" | "months" | "years";
|
type PickerView = "days" | "months" | "years";
|
||||||
@@ -54,7 +55,14 @@ export function MiniCalendar({
|
|||||||
const eventDates = useMemo(() => {
|
const eventDates = useMemo(() => {
|
||||||
const set = new Set<string>();
|
const set = new Set<string>();
|
||||||
events.forEach(e => {
|
events.forEach(e => {
|
||||||
try { set.add(format(parseISO(e.start), "yyyy-MM-dd")); } catch { /* skip */ }
|
try {
|
||||||
|
const { startDay, endDay } = getEventDayBounds(e);
|
||||||
|
const cursor = new Date(startDay);
|
||||||
|
while (cursor <= endDay) {
|
||||||
|
set.add(format(cursor, "yyyy-MM-dd"));
|
||||||
|
cursor.setDate(cursor.getDate() + 1);
|
||||||
|
}
|
||||||
|
} catch { /* skip */ }
|
||||||
});
|
});
|
||||||
return set;
|
return set;
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
getEventDayBounds,
|
getEventDayBounds,
|
||||||
getEventDisplayEndDate,
|
getEventDisplayEndDate,
|
||||||
getEventEndDate,
|
getEventEndDate,
|
||||||
|
getEventStartDate,
|
||||||
getTimedEventBoundsForDay,
|
getTimedEventBoundsForDay,
|
||||||
isTimedEventFullDayOnDate,
|
isTimedEventFullDayOnDate,
|
||||||
layoutOverlappingEvents,
|
layoutOverlappingEvents,
|
||||||
@@ -96,7 +97,21 @@ describe('calendar-utils all-day handling', () => {
|
|||||||
utcEnd: '2026-03-14T11:00:00Z',
|
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', () => {
|
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({
|
expect(getTimedEventBoundsForDay(event, new Date('2026-03-14T00:00:00Z'))).toMatchObject({
|
||||||
startMinutes: 1320,
|
startMinutes: 1380,
|
||||||
endMinutes: 1440,
|
endMinutes: 1440,
|
||||||
continuesBefore: false,
|
continuesBefore: false,
|
||||||
continuesAfter: true,
|
continuesAfter: true,
|
||||||
@@ -117,7 +132,7 @@ describe('calendar-utils all-day handling', () => {
|
|||||||
|
|
||||||
expect(getTimedEventBoundsForDay(event, new Date('2026-03-15T00:00:00Z'))).toMatchObject({
|
expect(getTimedEventBoundsForDay(event, new Date('2026-03-15T00:00:00Z'))).toMatchObject({
|
||||||
startMinutes: 0,
|
startMinutes: 0,
|
||||||
endMinutes: 120,
|
endMinutes: 180,
|
||||||
continuesBefore: true,
|
continuesBefore: true,
|
||||||
continuesAfter: false,
|
continuesAfter: false,
|
||||||
});
|
});
|
||||||
@@ -137,7 +152,7 @@ describe('calendar-utils all-day handling', () => {
|
|||||||
expect(layout).toHaveLength(1);
|
expect(layout).toHaveLength(1);
|
||||||
expect(layout[0]).toMatchObject({
|
expect(layout[0]).toMatchObject({
|
||||||
startMinutes: 0,
|
startMinutes: 0,
|
||||||
endMinutes: 120,
|
endMinutes: 180,
|
||||||
column: 0,
|
column: 0,
|
||||||
totalColumns: 1,
|
totalColumns: 1,
|
||||||
continuesBefore: true,
|
continuesBefore: true,
|
||||||
|
|||||||
+17
-5
@@ -21,6 +21,13 @@ export interface TimedEventLayout {
|
|||||||
continuesAfter: boolean;
|
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[] {
|
export function packWeekSegments(rawSegments: CalendarWeekSegment[]): CalendarWeekSegment[] {
|
||||||
rawSegments.sort((left, right) => {
|
rawSegments.sort((left, right) => {
|
||||||
if (left.startIndex !== right.startIndex) return left.startIndex - right.startIndex;
|
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) {
|
if (left.event.showWithoutTime !== right.event.showWithoutTime) {
|
||||||
return left.event.showWithoutTime ? -1 : 1;
|
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;
|
if (timeDiff !== 0) return timeDiff;
|
||||||
return (left.event.title || "").localeCompare(right.event.title || "");
|
return (left.event.title || "").localeCompare(right.event.title || "");
|
||||||
});
|
});
|
||||||
@@ -48,14 +55,19 @@ export function packWeekSegments(rawSegments: CalendarWeekSegment[]): CalendarWe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getEventEndDate(event: CalendarEvent): Date {
|
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;
|
if (!event.duration) return start;
|
||||||
return new Date(start.getTime() + parseDuration(event.duration) * 60000);
|
return new Date(start.getTime() + parseDuration(event.duration) * 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEventDisplayEndDate(event: CalendarEvent): Date {
|
export function getEventDisplayEndDate(event: CalendarEvent): Date {
|
||||||
const end = getEventEndDate(event);
|
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 end;
|
||||||
}
|
}
|
||||||
return subMilliseconds(end, 1);
|
return subMilliseconds(end, 1);
|
||||||
@@ -63,7 +75,7 @@ export function getEventDisplayEndDate(event: CalendarEvent): Date {
|
|||||||
|
|
||||||
export function getEventDayBounds(event: CalendarEvent): { startDay: Date; endDay: Date } {
|
export function getEventDayBounds(event: CalendarEvent): { startDay: Date; endDay: Date } {
|
||||||
return {
|
return {
|
||||||
startDay: startOfDay(parseISO(event.start)),
|
startDay: startOfDay(getEventStartDate(event)),
|
||||||
endDay: startOfDay(getEventDisplayEndDate(event)),
|
endDay: startOfDay(getEventDisplayEndDate(event)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -74,7 +86,7 @@ export function getTimedEventBoundsForDay(
|
|||||||
): { startMinutes: number; endMinutes: number; continuesBefore: boolean; continuesAfter: boolean } | null {
|
): { startMinutes: number; endMinutes: number; continuesBefore: boolean; continuesAfter: boolean } | null {
|
||||||
if (event.showWithoutTime) return null;
|
if (event.showWithoutTime) return null;
|
||||||
|
|
||||||
const eventStart = parseISO(event.start);
|
const eventStart = getEventStartDate(event);
|
||||||
const eventEnd = getEventEndDate(event);
|
const eventEnd = getEventEndDate(event);
|
||||||
const dayStart = startOfDay(day);
|
const dayStart = startOfDay(day);
|
||||||
const nextDayStart = addDays(dayStart, 1);
|
const nextDayStart = addDays(dayStart, 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user