diff --git a/website/src/utils/ical.test.ts b/website/src/utils/ical.test.ts index d0f1666b9b..da50477f39 100644 --- a/website/src/utils/ical.test.ts +++ b/website/src/utils/ical.test.ts @@ -174,7 +174,7 @@ describe(calculateNumericWeek, () => { new Date('2016-10-31T14:00+0800'), // 12 ]), ); - }); + }); test('generates exclusions for holidays', () => { // 2016 holidays @@ -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 }, @@ -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'], diff --git a/website/src/utils/ical.ts b/website/src/utils/ical.ts index e2fd27c49e..7ba6351bb1 100644 --- a/website/src/utils/ical.ts +++ b/website/src/utils/ical.ts @@ -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, @@ -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)), @@ -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)); + } }); });