Skip to content
Open
6 changes: 4 additions & 2 deletions website/src/utils/ical.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe(calculateNumericWeek, () => {
new Date('2016-10-31T14:00+0800'), // 12
]),
);
});
});

test('generates exclusions for holidays', () => {
// 2016 holidays
Expand Down Expand Up @@ -258,8 +258,9 @@ test('iCalEventForLesson generates correct output', () => {
description: 'Personal Development & Career Management\nSectional Teaching Group A1',
location: 'BIZ1-0303',
repeating: {
interval: 1,
freq: 'WEEKLY',
count: 14,
count: 6,
byDay: ['Mo'],
exclude: expect.arrayContaining([]), // Tested in previous tests
},
Expand Down Expand Up @@ -291,6 +292,7 @@ test('work for half hour lesson offsets', () => {
description: 'Personal Development & Career Management\nSectional Teaching Group A1',
location: 'BIZ1-0303',
repeating: {
interval: 1,
freq: 'WEEKLY',
count: 14,
byDay: ['Mo'],
Expand Down
36 changes: 34 additions & 2 deletions website/src/utils/ical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ function calculateStartEnd(date: Date, startTime: StartTime, endTime: EndTime) {
return { start, end };
}

function calculateLargestInterval(weeks: NumericWeeks) {
let largestInterval = 1;
for (let i = 0; i < weeks.length - 1; i++) {
const diff = weeks[i + 1] - weeks[i];
if (diff > largestInterval) {
largestInterval = diff;
}
}
return largestInterval;
}

function isSplitOverRecess(weeks: NumericWeeks) {
return weeks.some((week) => week <= 6) && weeks.some((week) => week > 6);
}

export function calculateNumericWeek(
lesson: RawLesson,
_semester: Semester,
Expand All @@ -91,12 +106,21 @@ export function calculateNumericWeek(
const { start, end } = calculateStartEnd(lessonDay, lesson.startTime, lesson.endTime);
const excludedWeeks = _.difference([RECESS_WEEK, ...ALL_WEEKS], weeks);

// Sets interval to 2 for odd and even weeks. Fix for mobile GCal imports.
const isAlternate =
weeks.every((week) => ODD_WEEKS.includes(week)) ||
weeks.every((week) => EVEN_WEEKS.includes(week));
const interval = isAlternate ? 2 : 1;
const largestInterval = calculateLargestInterval(weeks);
const adjCount = ((largestInterval === interval) && !isSplitOverRecess(weeks)) ? weeks.length : NUM_WEEKS_IN_A_SEM;

return {
start,
end,
repeating: {
interval,
freq: 'WEEKLY',
count: NUM_WEEKS_IN_A_SEM,
count: adjCount,
byDay: [lesson.day.slice(0, 2)],
exclude: [
...excludedWeeks.map((week) => datesForAcademicWeeks(start, week)),
Expand Down Expand Up @@ -187,7 +211,15 @@ export default function iCalForTimetable(

_.each(lessonConfig, (lessons) => {
lessons.forEach((lesson) => {
events.push(iCalEventForLesson(lesson, moduleData[moduleCode], semester, firstDayOfSchool));
// If the lesson is split across recess week, we need to create two separate events for mobile GCal imports as it does not support exclusion rules.
if (isSplitOverRecess(lesson.weeks) && lesson.weeks.length <= NUM_WEEKS_IN_A_SEM/2) {
const lessonsFirstHalf = lesson.weeks.slice(0, 6);
const lessonsSecondHalf = lesson.weeks.slice(6);
events.push(iCalEventForLesson(lessonsFirstHalf, moduleData[moduleCode], semester, firstDayOfSchool));
events.push(iCalEventForLesson(lessonsSecondHalf, moduleData[moduleCode], semester, firstDayOfSchool));
} else {
events.push(iCalEventForLesson(lesson, moduleData[moduleCode], semester, firstDayOfSchool));
}
});
});

Expand Down