diff --git a/components/calendar/calendar-agenda-view.tsx b/components/calendar/calendar-agenda-view.tsx index e8d1c391..15712ff1 100644 --- a/components/calendar/calendar-agenda-view.tsx +++ b/components/calendar/calendar-agenda-view.tsx @@ -49,7 +49,7 @@ export function CalendarAgendaView({ const grouped = useMemo(() => { const sorted = [...events].sort((a, b) => - new Date(a.start).getTime() - new Date(b.start).getTime() + parseISO(a.start).getTime() - parseISO(b.start).getTime() ); const groups: DayGroup[] = []; diff --git a/components/calendar/calendar-month-view.tsx b/components/calendar/calendar-month-view.tsx index be21714c..dfccf673 100644 --- a/components/calendar/calendar-month-view.tsx +++ b/components/calendar/calendar-month-view.tsx @@ -121,13 +121,14 @@ export function CalendarMonthView({ try { const data = JSON.parse(json); const originalStart = parseISO(data.originalStart); + const event = useCalendarStore.getState().events.find(e => e.id === data.eventId); + const isAllDay = event?.showWithoutTime; const newStart = new Date(day); newStart.setHours(originalStart.getHours(), originalStart.getMinutes(), originalStart.getSeconds(), 0); - const newStartISO = format(newStart, "yyyy-MM-dd'T'HH:mm:ss"); + const newStartISO = isAllDay ? format(newStart, "yyyy-MM-dd") : format(newStart, "yyyy-MM-dd'T'HH:mm:ss"); if (newStartISO === data.originalStart) return; const client = useAuthStore.getState().client; if (!client) return; - const event = useCalendarStore.getState().events.find(e => e.id === data.eventId); const hasParticipants = event?.participants && Object.keys(event.participants).length > 0; await useCalendarStore.getState().updateEvent(client, data.eventId, { start: newStartISO }, hasParticipants || undefined); } catch { diff --git a/components/calendar/event-modal.tsx b/components/calendar/event-modal.tsx index a45e359e..2751ccac 100644 --- a/components/calendar/event-modal.tsx +++ b/components/calendar/event-modal.tsx @@ -262,10 +262,10 @@ export function EventModal({ if (trimmedTitle.length > 500 || description.trim().length > 10000 || location.trim().length > 500) return; const startStr = allDay - ? `${startDate}T00:00:00` + ? startDate : `${startDate}T${startTime}:00`; - const start = new Date(startStr); + const start = allDay ? parseISO(startStr) : new Date(startStr); let duration: string; if (allDay) { @@ -393,7 +393,7 @@ export function EventModal({ uid: newUid, title: event.title, description: event.description, - start: format(newStart, "yyyy-MM-dd'T'HH:mm:ss"), + start: event.showWithoutTime ? format(newStart, "yyyy-MM-dd") : format(newStart, "yyyy-MM-dd'T'HH:mm:ss"), duration: event.duration, timeZone: event.timeZone, showWithoutTime: event.showWithoutTime, diff --git a/components/calendar/mini-calendar.tsx b/components/calendar/mini-calendar.tsx index 47578533..90ed0898 100644 --- a/components/calendar/mini-calendar.tsx +++ b/components/calendar/mini-calendar.tsx @@ -7,7 +7,7 @@ import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, addMonths, subMonths, addYears, subYears, setMonth, setYear, eachDayOfInterval, getMonth, getYear, getISOWeek, getWeek, - isSameDay, isSameMonth, isToday, format, + isSameDay, isSameMonth, isToday, format, parseISO, } from "date-fns"; import { cn } from "@/lib/utils"; import type { CalendarEvent } from "@/lib/jmap/types"; @@ -54,7 +54,7 @@ export function MiniCalendar({ const eventDates = useMemo(() => { const set = new Set(); events.forEach(e => { - try { set.add(format(new Date(e.start), "yyyy-MM-dd")); } catch { /* skip */ } + try { set.add(format(parseISO(e.start), "yyyy-MM-dd")); } catch { /* skip */ } }); return set; }, [events]); diff --git a/components/calendar/task-modal.tsx b/components/calendar/task-modal.tsx index 683db859..bf4c11e0 100644 --- a/components/calendar/task-modal.tsx +++ b/components/calendar/task-modal.tsx @@ -97,7 +97,7 @@ export function TaskModal({ due = `${dueDate}T${dueTime}:00`; showWithoutTime = false; } else { - due = `${dueDate}T00:00:00`; + due = dueDate; showWithoutTime = true; } } diff --git a/hooks/use-calendar-alerts.ts b/hooks/use-calendar-alerts.ts index 6f9feba8..733fe91d 100644 --- a/hooks/use-calendar-alerts.ts +++ b/hooks/use-calendar-alerts.ts @@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback } from 'react'; import { useTranslations, useLocale } from 'next-intl'; +import { parseISO } from 'date-fns'; import { useAuthStore } from '@/stores/auth-store'; import { useCalendarStore } from '@/stores/calendar-store'; import { useSettingsStore } from '@/stores/settings-store'; @@ -50,7 +51,7 @@ export function useCalendarAlerts() { playNotificationSound(notificationSoundChoice); } - const diffMs = new Date(alert.event.utcStart || alert.event.start).getTime() - now; + const diffMs = (alert.event.utcStart ? new Date(alert.event.utcStart).getTime() : parseISO(alert.event.start).getTime()) - now; const diffMin = Math.round(diffMs / 60000); const timeLabel = diffMin <= 0 diff --git a/lib/calendar-alerts.ts b/lib/calendar-alerts.ts index ac29bacb..69de556f 100644 --- a/lib/calendar-alerts.ts +++ b/lib/calendar-alerts.ts @@ -1,3 +1,4 @@ +import { parseISO } from 'date-fns'; import type { CalendarEvent, CalendarEventAlert, @@ -53,7 +54,7 @@ export function computeFireTime( baseTime = new Date(event.utcEnd).getTime(); } else { // Compute end from start + duration - const startMs = new Date(event.start).getTime(); + const startMs = parseISO(event.start).getTime(); if (Number.isNaN(startMs)) return null; const durationMin = parseDuration(event.duration); baseTime = startMs + durationMin * 60000; @@ -61,7 +62,7 @@ export function computeFireTime( } else { baseTime = event.utcStart ? new Date(event.utcStart).getTime() - : new Date(event.start).getTime(); + : parseISO(event.start).getTime(); } if (Number.isNaN(baseTime)) return null; diff --git a/lib/calendar-invitation.ts b/lib/calendar-invitation.ts index 5925aadf..b1dbad26 100644 --- a/lib/calendar-invitation.ts +++ b/lib/calendar-invitation.ts @@ -1,3 +1,4 @@ +import { parseISO } from 'date-fns'; import type { Email, Attachment, CalendarEvent, CalendarParticipant, EmailBodyPart } from '@/lib/jmap/types'; export type InvitationMethod = @@ -534,7 +535,7 @@ function addDurationToDate(start: string, duration: string, _timeZone?: string | const minutes = parseInt(match[4] || '0'); const seconds = parseInt(match[5] || '0'); - const date = new Date(start); + const date = parseISO(start); if (isNaN(date.getTime())) return null; const isUTC = start.endsWith('Z') || start.includes('+'); diff --git a/lib/calendar-utils.ts b/lib/calendar-utils.ts index 17f63209..7a8e98f3 100644 --- a/lib/calendar-utils.ts +++ b/lib/calendar-utils.ts @@ -12,14 +12,14 @@ export interface CalendarWeekSegment { } export function getEventEndDate(event: CalendarEvent): Date { - const start = new Date(event.start); + const start = parseISO(event.start); 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() <= new Date(event.start).getTime()) { + if (!event.showWithoutTime || end.getTime() <= parseISO(event.start).getTime()) { return end; } return subMilliseconds(end, 1); @@ -27,7 +27,7 @@ export function getEventDisplayEndDate(event: CalendarEvent): Date { export function getEventDayBounds(event: CalendarEvent): { startDay: Date; endDay: Date } { return { - startDay: startOfDay(new Date(event.start)), + startDay: startOfDay(parseISO(event.start)), endDay: startOfDay(getEventDisplayEndDate(event)), }; } @@ -77,7 +77,7 @@ export function buildWeekSegments(events: CalendarEvent[], weekDays: Date[]): Ca if (left.event.showWithoutTime !== right.event.showWithoutTime) { return left.event.showWithoutTime ? -1 : 1; } - const timeDiff = new Date(left.event.start).getTime() - new Date(right.event.start).getTime(); + const timeDiff = parseISO(left.event.start).getTime() - parseISO(right.event.start).getTime(); if (timeDiff !== 0) return timeDiff; return (left.event.title || "").localeCompare(right.event.title || ""); }); @@ -100,7 +100,7 @@ export function layoutOverlappingEvents( events: CalendarEvent[], ): { event: CalendarEvent; column: number; totalColumns: number }[] { const sorted = [...events].sort((a, b) => { - const diff = new Date(a.start).getTime() - new Date(b.start).getTime(); + const diff = parseISO(a.start).getTime() - parseISO(b.start).getTime(); if (diff !== 0) return diff; return parseDuration(b.duration) - parseDuration(a.duration); });