Skip to content
Open
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
22 changes: 12 additions & 10 deletions src/controls/calendar/hooks/useCalendar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable no-unmodified-loop-condition */
import { format } from 'date-fns';
import { IEvent } from '../models/IEvents';
import { useCallback, } from 'react';
import { v4 as uuidv4 } from 'uuid'; // Use UUID for generating unique IDs
Expand Down Expand Up @@ -39,9 +40,10 @@ export const useCalendar = (timezone: string): IUseCalendar => {
// Memoized helper for timezone handling
const toLocalDate = useCallback(
(dateString: string): Date => {
return new Date(
new Date(dateString).toLocaleString(undefined, { timeZone: timezone })
const localDate = new Date(
new Date(dateString).toLocaleString('en-US', { timeZone: timezone })
);
return localDate
},
[timezone]
);
Expand All @@ -59,7 +61,7 @@ export const useCalendar = (timezone: string): IUseCalendar => {

for (let day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const dateString = date.toISOString().split('T')[0];
const dateString = format(date, 'yyyy-MM-dd');
calendarEventsByDay[dateString] = [];
}

Expand All @@ -69,7 +71,7 @@ export const useCalendar = (timezone: string): IUseCalendar => {
const currentDate = new Date(eventStart);

while (currentDate <= eventEnd) {
const dateString = currentDate.toISOString().split('T')[0];
const dateString = format(currentDate, 'yyyy-MM-dd');
if (calendarEventsByDay[dateString]) {
calendarEventsByDay[dateString].push(event);
}
Expand All @@ -91,7 +93,7 @@ export const useCalendar = (timezone: string): IUseCalendar => {
for (let i = 0; i < 7; i++) {
const currentDate = new Date(start);
currentDate.setDate(start.getDate() + i);
const dateString = currentDate.toISOString().split('T')[0];
const dateString = format(currentDate, 'yyyy-MM-dd');

const dayTimeSlots: TimeSlot[] = Array.from(
{ length: 48 },
Expand All @@ -111,21 +113,21 @@ export const useCalendar = (timezone: string): IUseCalendar => {

if (event.isFullDay) {
if (
eventStart.toISOString().split('T')[0] <= dateString &&
eventEnd.toISOString().split('T')[0] >= dateString
format(eventStart, 'yyyy-MM-dd') <= dateString &&
format(eventEnd, 'yyyy-MM-dd') >= dateString
) {
fullDayEvents.push(event);
}
return;
}

if (
eventStart.toISOString().split('T')[0] <= dateString &&
eventEnd.toISOString().split('T')[0] >= dateString
format(eventStart, 'yyyy-MM-dd') <= dateString &&
format(eventEnd, 'yyyy-MM-dd') >= dateString
) {
const currentSlot = new Date(eventStart);
while (currentSlot <= eventEnd) {
const slotDateString = currentSlot.toISOString().split('T')[0];
const slotDateString = format(currentSlot, 'yyyy-MM-dd');
if (slotDateString === dateString) {
const slotIndex =
currentSlot.getHours() * 2 +
Expand Down
81 changes: 81 additions & 0 deletions src/webparts/controlsTest/components/TestCalendarControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,87 @@ export const mockEvents: IEvent[] = [
imageUrl: "https://via.placeholder.com/150",
color: "magenta",
},
// midnight event — UTC+10 users: 00:30 local
{
id: "11",
title: "Midnight Deployment",
start: "2026-06-14T00:30:00",
end: "2026-06-14T01:00:00",
category: "Task",
importance: "High",
description: "Edge case: early morning event (00:30 local). Fails to render in month view without the date key fix for UTC+ timezones.",
enableOnHover: true,
color: "steel",
webLink: null,
},
// 02:00 local — UTC+4 users: 02:00 local
{
id: "12",
title: "Early Morning Sync",
start: "2026-06-15T02:00:00",
end: "2026-06-15T02:30:00",
category: "Meeting",
importance: "Medium",
description: "Edge case: 02:00 local. Misplaced in month view for UTC+4 users without the date key fix.",
enableOnHover: true,
color: "lavender",
webLink: null,
},
// event spanning midnight
{
id: "13",
title: "Overnight On-call",
start: "2026-06-17T23:00:00",
end: "2026-06-18T01:00:00",
category: "Task",
importance: "High",
description: "Edge case: spans midnight. Should appear on both Jun 15 and Jun 16.",
enableOnHover: true,
color: "cranberry",
webLink: null,
},
// negative offset (UTC-5, New York)
{
id: "14",
title: "New York All-Hands (UTC-5)",
start: "2026-06-19T14:00:00-05:00",
end: "2026-06-19T15:00:00-05:00",
category: "Meeting",
location: "New York HQ",
importance: "High",
description: "Timezone test: negative offset UTC-5. Stored as 19:00 UTC — each user sees it in their local time.",
enableOnHover: true,
color: "navy",
webLink: "https://outlook.com",
},
// zero offset (UTC)
{
id: "15",
title: "London Sprint Review (UTC+0)",
start: "2026-06-20T09:00:00Z",
end: "2026-06-20T10:00:00Z",
category: "Meeting",
location: "London Office",
importance: "Medium",
description: "Timezone test: UTC (Z suffix). Each user sees it in their local time.",
enableOnHover: true,
color: "anchor",
webLink: "https://outlook.com",
},
// positive offset (UTC+5:30, India)
{
id: "16",
title: "Mumbai Design Review (UTC+5:30)",
start: "2026-06-20T15:30:00+05:30",
end: "2026-06-20T16:30:00+05:30",
category: "Meeting",
location: "Mumbai Office",
importance: "Medium",
description: "Timezone test: positive offset UTC+5:30. Stored as 10:00 UTC — each user sees it in their local time.",
enableOnHover: true,
color: "brass",
webLink: "https://outlook.com",
},
];

export const TestCalendarControl: React.FunctionComponent<ICalendarProps> = (
Expand Down