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
19 changes: 19 additions & 0 deletions src/lib/ics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,23 @@ describe("exportICS", () => {
const eventCount = (ics.match(/BEGIN:VEVENT/g) || []).length;
expect(eventCount).toBe(1);
});

it("correctly exports section when section numbers have gaps", () => {
// Simulate real Caltech data where section numbers skip (e.g., 1, 2, 4, 5)
const section1 = makeSection(1, "M 09:00 - 09:55", "Baxter 101");
const section2 = makeSection(2, "T 10:00 - 10:55", "Baxter 102");
const section4 = makeSection(4, "W 11:00 - 11:55", "Sloan 151");
const section5 = makeSection(5, "R 13:00 - 13:55", "Sloan 152");
const courseData = makeCourseData(1, "CS 1", [section1, section2, section4, section5]);

// sectionId 2 = array index 2 = section number 4 (Wednesday)
const course = makeCourse(courseData, { sectionId: 2 });

const ics = exportICS("sp2026", [course]);

expect(ics).toContain("BEGIN:VEVENT");
const eventCount = (ics.match(/BEGIN:VEVENT/g) || []).length;
expect(eventCount).toBe(1);
expect(ics).toContain("LOCATION:Sloan 151");
});
});
41 changes: 20 additions & 21 deletions src/lib/ics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,27 @@ export function exportICS(term: string, courses: CourseStorage[]): string {

// Flatten the courses and parse times with start and end times, matching locations
const parsedEvents = courses
.filter(course => course.enabled)
.filter(course => course.enabled && course.sectionId !== null)
.flatMap(course => {
return course.courseData.sections
.filter(section => section.number - 1 === course.sectionId) // Filter by selected section
.flatMap(section => {
const times = section.times.split('\n'); // Split multiple times on newline
const locations = section.locations.split('\n'); // Split multiple locations on newline

// Zip times and locations together
return times.flatMap((time, index) => {
const location = locations[index] || 'Unknown'; // Match time with corresponding location
const [days, startTime, , endTime] = time.split(' '); // Separate days and time range
if (days === 'A') return []; // skip to-be-announced times

return days.split('').map(day => ({
name: course.courseData.number, // Use course number for the title
location, // Set the matched location for this time
startTime: getFirstOccurrence(termStartDate, day, startTime),
endTime: getFirstOccurrence(termStartDate, day, endTime) // Parse the end time
}));
});
});
const section = course.courseData.sections[course.sectionId!];
if (!section) return [];

const times = section.times.split('\n'); // Split multiple times on newline
const locations = section.locations.split('\n'); // Split multiple locations on newline

// Zip times and locations together
return times.flatMap((time, index) => {
const location = locations[index] || 'Unknown'; // Match time with corresponding location
const [days, startTime, , endTime] = time.split(' '); // Separate days and time range
if (days === 'A') return []; // skip to-be-announced times

return days.split('').map(day => ({
name: course.courseData.number, // Use course number for the title
location, // Set the matched location for this time
startTime: getFirstOccurrence(termStartDate, day, startTime),
endTime: getFirstOccurrence(termStartDate, day, endTime) // Parse the end time
}));
});
});

// Create a basic ICS header using stable floating local times.
Expand Down
Loading