feat: replace Date constructor with parseISO for improved date handling across calendar components #25
This commit is contained in:
@@ -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) =>
|
||||||
new Date(a.start).getTime() - new Date(b.start).getTime()
|
parseISO(a.start).getTime() - parseISO(b.start).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
const groups: DayGroup[] = [];
|
const groups: DayGroup[] = [];
|
||||||
|
|||||||
@@ -121,13 +121,14 @@ export function CalendarMonthView({
|
|||||||
try {
|
try {
|
||||||
const data = JSON.parse(json);
|
const data = JSON.parse(json);
|
||||||
const originalStart = parseISO(data.originalStart);
|
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);
|
const newStart = new Date(day);
|
||||||
newStart.setHours(originalStart.getHours(), originalStart.getMinutes(), originalStart.getSeconds(), 0);
|
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;
|
if (newStartISO === data.originalStart) return;
|
||||||
const client = useAuthStore.getState().client;
|
const client = useAuthStore.getState().client;
|
||||||
if (!client) return;
|
if (!client) return;
|
||||||
const event = useCalendarStore.getState().events.find(e => e.id === data.eventId);
|
|
||||||
const hasParticipants = event?.participants && Object.keys(event.participants).length > 0;
|
const hasParticipants = event?.participants && Object.keys(event.participants).length > 0;
|
||||||
await useCalendarStore.getState().updateEvent(client, data.eventId, { start: newStartISO }, hasParticipants || undefined);
|
await useCalendarStore.getState().updateEvent(client, data.eventId, { start: newStartISO }, hasParticipants || undefined);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -262,10 +262,10 @@ export function EventModal({
|
|||||||
if (trimmedTitle.length > 500 || description.trim().length > 10000 || location.trim().length > 500) return;
|
if (trimmedTitle.length > 500 || description.trim().length > 10000 || location.trim().length > 500) return;
|
||||||
|
|
||||||
const startStr = allDay
|
const startStr = allDay
|
||||||
? `${startDate}T00:00:00`
|
? startDate
|
||||||
: `${startDate}T${startTime}:00`;
|
: `${startDate}T${startTime}:00`;
|
||||||
|
|
||||||
const start = new Date(startStr);
|
const start = allDay ? parseISO(startStr) : new Date(startStr);
|
||||||
let duration: string;
|
let duration: string;
|
||||||
|
|
||||||
if (allDay) {
|
if (allDay) {
|
||||||
@@ -393,7 +393,7 @@ export function EventModal({
|
|||||||
uid: newUid,
|
uid: newUid,
|
||||||
title: event.title,
|
title: event.title,
|
||||||
description: event.description,
|
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,
|
duration: event.duration,
|
||||||
timeZone: event.timeZone,
|
timeZone: event.timeZone,
|
||||||
showWithoutTime: event.showWithoutTime,
|
showWithoutTime: event.showWithoutTime,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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,
|
isSameDay, isSameMonth, isToday, format, parseISO,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { CalendarEvent } from "@/lib/jmap/types";
|
import type { CalendarEvent } from "@/lib/jmap/types";
|
||||||
@@ -54,7 +54,7 @@ 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(new Date(e.start), "yyyy-MM-dd")); } catch { /* skip */ }
|
try { set.add(format(parseISO(e.start), "yyyy-MM-dd")); } catch { /* skip */ }
|
||||||
});
|
});
|
||||||
return set;
|
return set;
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function TaskModal({
|
|||||||
due = `${dueDate}T${dueTime}:00`;
|
due = `${dueDate}T${dueTime}:00`;
|
||||||
showWithoutTime = false;
|
showWithoutTime = false;
|
||||||
} else {
|
} else {
|
||||||
due = `${dueDate}T00:00:00`;
|
due = dueDate;
|
||||||
showWithoutTime = true;
|
showWithoutTime = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useRef, useCallback } from 'react';
|
import { useEffect, useRef, useCallback } from 'react';
|
||||||
import { useTranslations, useLocale } from 'next-intl';
|
import { useTranslations, useLocale } from 'next-intl';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
import { useAuthStore } from '@/stores/auth-store';
|
import { useAuthStore } from '@/stores/auth-store';
|
||||||
import { useCalendarStore } from '@/stores/calendar-store';
|
import { useCalendarStore } from '@/stores/calendar-store';
|
||||||
import { useSettingsStore } from '@/stores/settings-store';
|
import { useSettingsStore } from '@/stores/settings-store';
|
||||||
@@ -50,7 +51,7 @@ export function useCalendarAlerts() {
|
|||||||
playNotificationSound(notificationSoundChoice);
|
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 diffMin = Math.round(diffMs / 60000);
|
||||||
|
|
||||||
const timeLabel = diffMin <= 0
|
const timeLabel = diffMin <= 0
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { parseISO } from 'date-fns';
|
||||||
import type {
|
import type {
|
||||||
CalendarEvent,
|
CalendarEvent,
|
||||||
CalendarEventAlert,
|
CalendarEventAlert,
|
||||||
@@ -53,7 +54,7 @@ export function computeFireTime(
|
|||||||
baseTime = new Date(event.utcEnd).getTime();
|
baseTime = new Date(event.utcEnd).getTime();
|
||||||
} else {
|
} else {
|
||||||
// Compute end from start + duration
|
// Compute end from start + duration
|
||||||
const startMs = new Date(event.start).getTime();
|
const startMs = parseISO(event.start).getTime();
|
||||||
if (Number.isNaN(startMs)) return null;
|
if (Number.isNaN(startMs)) return null;
|
||||||
const durationMin = parseDuration(event.duration);
|
const durationMin = parseDuration(event.duration);
|
||||||
baseTime = startMs + durationMin * 60000;
|
baseTime = startMs + durationMin * 60000;
|
||||||
@@ -61,7 +62,7 @@ export function computeFireTime(
|
|||||||
} else {
|
} else {
|
||||||
baseTime = event.utcStart
|
baseTime = event.utcStart
|
||||||
? new Date(event.utcStart).getTime()
|
? new Date(event.utcStart).getTime()
|
||||||
: new Date(event.start).getTime();
|
: parseISO(event.start).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(baseTime)) return null;
|
if (Number.isNaN(baseTime)) return null;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { parseISO } from 'date-fns';
|
||||||
import type { Email, Attachment, CalendarEvent, CalendarParticipant, EmailBodyPart } from '@/lib/jmap/types';
|
import type { Email, Attachment, CalendarEvent, CalendarParticipant, EmailBodyPart } from '@/lib/jmap/types';
|
||||||
|
|
||||||
export type InvitationMethod =
|
export type InvitationMethod =
|
||||||
@@ -534,7 +535,7 @@ function addDurationToDate(start: string, duration: string, _timeZone?: string |
|
|||||||
const minutes = parseInt(match[4] || '0');
|
const minutes = parseInt(match[4] || '0');
|
||||||
const seconds = parseInt(match[5] || '0');
|
const seconds = parseInt(match[5] || '0');
|
||||||
|
|
||||||
const date = new Date(start);
|
const date = parseISO(start);
|
||||||
if (isNaN(date.getTime())) return null;
|
if (isNaN(date.getTime())) return null;
|
||||||
|
|
||||||
const isUTC = start.endsWith('Z') || start.includes('+');
|
const isUTC = start.endsWith('Z') || start.includes('+');
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ export interface CalendarWeekSegment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getEventEndDate(event: CalendarEvent): Date {
|
export function getEventEndDate(event: CalendarEvent): Date {
|
||||||
const start = new Date(event.start);
|
const start = parseISO(event.start);
|
||||||
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() <= new Date(event.start).getTime()) {
|
if (!event.showWithoutTime || end.getTime() <= parseISO(event.start).getTime()) {
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
return subMilliseconds(end, 1);
|
return subMilliseconds(end, 1);
|
||||||
@@ -27,7 +27,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(new Date(event.start)),
|
startDay: startOfDay(parseISO(event.start)),
|
||||||
endDay: startOfDay(getEventDisplayEndDate(event)),
|
endDay: startOfDay(getEventDisplayEndDate(event)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ export function buildWeekSegments(events: CalendarEvent[], weekDays: Date[]): Ca
|
|||||||
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 = 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;
|
if (timeDiff !== 0) return timeDiff;
|
||||||
return (left.event.title || "").localeCompare(right.event.title || "");
|
return (left.event.title || "").localeCompare(right.event.title || "");
|
||||||
});
|
});
|
||||||
@@ -100,7 +100,7 @@ export function layoutOverlappingEvents(
|
|||||||
events: CalendarEvent[],
|
events: CalendarEvent[],
|
||||||
): { event: CalendarEvent; column: number; totalColumns: number }[] {
|
): { event: CalendarEvent; column: number; totalColumns: number }[] {
|
||||||
const sorted = [...events].sort((a, b) => {
|
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;
|
if (diff !== 0) return diff;
|
||||||
return parseDuration(b.duration) - parseDuration(a.duration);
|
return parseDuration(b.duration) - parseDuration(a.duration);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user