Skip to content

Commit 856792e

Browse files
authored
fix: slots ooo bug (calcom#25878)
1 parent 52d5261 commit 856792e

2 files changed

Lines changed: 116 additions & 2 deletions

File tree

packages/features/schedules/lib/slots.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,4 +896,119 @@ describe("Tests the date-range slot logic with showOptimizedSlots", () => {
896896

897897
vi.useRealTimers();
898898
});
899+
900+
it("should mark slots as away for cross-timezone OOO (Berlin OOO viewed from Kolkata)", async () => {
901+
vi.setSystemTime(dayjs.utc("2024-12-20T00:00:00Z").toDate());
902+
903+
const datesOutOfOffice = {
904+
"2024-12-22": {
905+
fromUser: { id: 1, displayName: "Test User" },
906+
toUser: null,
907+
reason: "Holiday",
908+
emoji: "🎄",
909+
},
910+
};
911+
912+
const dateRanges = [
913+
{
914+
start: dayjs.tz("2024-12-22T09:00:00", "Europe/Berlin"),
915+
end: dayjs.tz("2024-12-22T18:00:00", "Europe/Berlin"),
916+
},
917+
];
918+
919+
const inviteeDate = dayjs.tz("2024-12-22T13:30:00", "Asia/Kolkata");
920+
921+
const slots = getSlots({
922+
inviteeDate,
923+
frequency: 30,
924+
minimumBookingNotice: 0,
925+
eventLength: 30,
926+
dateRanges,
927+
datesOutOfOffice,
928+
});
929+
930+
expect(slots.length).toBeGreaterThan(0);
931+
slots.forEach((slot) => {
932+
expect(slot.away).toBe(true);
933+
expect(slot.reason).toBe("Holiday");
934+
});
935+
936+
vi.useRealTimers();
937+
});
938+
939+
it("should correctly handle OOO when slot time crosses UTC date boundary", async () => {
940+
vi.setSystemTime(dayjs.utc("2024-12-20T00:00:00Z").toDate());
941+
942+
const datesOutOfOffice = {
943+
"2024-12-22": {
944+
fromUser: { id: 1, displayName: "Test User" },
945+
toUser: null,
946+
reason: "OOO",
947+
emoji: "🏖️",
948+
},
949+
};
950+
951+
const dateRanges = [
952+
{
953+
start: dayjs.utc("2024-12-22T22:00:00Z"),
954+
end: dayjs.utc("2024-12-22T23:59:59Z"),
955+
},
956+
];
957+
958+
const inviteeDate = dayjs.tz("2024-12-23T03:30:00", "Asia/Kolkata");
959+
960+
const slots = getSlots({
961+
inviteeDate,
962+
frequency: 30,
963+
minimumBookingNotice: 0,
964+
eventLength: 30,
965+
dateRanges,
966+
datesOutOfOffice,
967+
});
968+
969+
expect(slots.length).toBeGreaterThan(0);
970+
slots.forEach((slot) => {
971+
expect(slot.away).toBe(true);
972+
});
973+
974+
vi.useRealTimers();
975+
});
976+
977+
it("should NOT mark slots as away when they fall outside OOO UTC date", async () => {
978+
vi.setSystemTime(dayjs.utc("2024-12-20T00:00:00Z").toDate());
979+
980+
const datesOutOfOffice = {
981+
"2024-12-22": {
982+
fromUser: { id: 1, displayName: "Test User" },
983+
toUser: null,
984+
reason: "OOO",
985+
emoji: "🏖️",
986+
},
987+
};
988+
989+
const dateRanges = [
990+
{
991+
start: dayjs.utc("2024-12-21T17:00:00Z"),
992+
end: dayjs.utc("2024-12-21T20:00:00Z"),
993+
},
994+
];
995+
996+
const inviteeDate = dayjs.tz("2024-12-21T22:30:00", "Asia/Kolkata");
997+
998+
const slots = getSlots({
999+
inviteeDate,
1000+
frequency: 30,
1001+
minimumBookingNotice: 0,
1002+
eventLength: 30,
1003+
dateRanges,
1004+
datesOutOfOffice,
1005+
});
1006+
1007+
expect(slots.length).toBeGreaterThan(0);
1008+
slots.forEach((slot) => {
1009+
expect(slot.away).toBeUndefined();
1010+
});
1011+
1012+
vi.useRealTimers();
1013+
});
8991014
});

packages/features/schedules/lib/slots.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ function buildSlotsWithDateRanges({
122122
const slotBoundaries = new Map<number, true>();
123123

124124
orderedDateRanges.forEach((range) => {
125-
126125
let slotStartTime = range.start.utc().isAfter(startTimeWithMinNotice)
127126
? range.start
128127
: startTimeWithMinNotice;
@@ -181,7 +180,7 @@ function buildSlotsWithDateRanges({
181180
}
182181

183182
slotBoundaries.set(slotStartTime.valueOf(), true);
184-
const slotDateYYYYMMDD = slotStartTime.format("YYYY-MM-DD");
183+
const slotDateYYYYMMDD = slotStartTime.utc().format("YYYY-MM-DD");
185184
const dateOutOfOfficeExists = datesOutOfOffice?.[slotDateYYYYMMDD];
186185
let slotData: {
187186
time: Dayjs;

0 commit comments

Comments
 (0)