Skip to content

Generate one ical event for meetings instead of one per weekday #339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
152 changes: 108 additions & 44 deletions api/src/Schedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ private function icalFormatTime($time) {
. str_pad($min, 2, '0', STR_PAD_LEFT)
. "00";
}

private function firstDayAfterDate($weekday, $startDate) {
$weekdayOfStart = date('w', $startDate);
if ($weekdayOfStart > $weekday) {
Expand Down Expand Up @@ -50,59 +50,123 @@ public function generateIcal($schedule) {
// Start generating code
$code = "";

// Header
$code .= "BEGIN:VCALENDAR\r\n";
$code .= "VERSION:2.0\r\n";
$code .= "PRODID: -//CSH ScheduleMaker//iCal4j 1.0//EN\r\n";
$code .= "METHOD:PUBLISH\r\n";
$code .= "CALSCALE:GREGORIAN\r\n";

// Iterate over all the courses
foreach($schedule['courses'] as $course) {
// Skip classes that don't meet
if(empty($course['times'])) {
continue;
}

// Iterate over all the times
foreach($course['times'] as $time) {
$code .= "BEGIN:VEVENT\r\n";
$code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}");
$code .= "\r\n";
$code .= "TZID:America/New_York\r\n";
$code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n";

$startTime = $this->icalFormatTime($time['start']);
$endTime = $this->icalFormatTime($time['end']);

// The start day of the event MUST be offset by it's day
// the -1 is b/c quarter starts are on Monday(=1)
// This /could/ be done via the RRULE WKST param, but that means
// translating days from numbers to some other esoteric format.
// @TODO: Retrieve the timezone from php or the config file
$day = date("Ymd", $this->firstDayAfterDate($time['day'], $termStart));

$code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n";
$code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n";
$code .= "RRULE:FREQ=WEEKLY;UNTIL={$termEnd}\r\n";
$code .= "ORGANIZER:RIT\r\n";

// Course name
$code .= "SUMMARY:{$course['title']}";
if($course['courseNum'] != 'non') {
$code .= " ({$course['courseNum']})";
else {
// Get all the times for this course
$times = array(); // Instantiate an array to hold the details of each time the course meets
$checker = array(); // Allows us to check if all the details are the same across each time the course meets
foreach($course['times'] as $time) { // Iterate over each time the course meets
// Add the details of the course to the $times array for use when generating the event
array_push($times, array(
"day" => (int)$time['day'],
"start" => (int)$time['start'],
"end" => (int)$time['end'],
"bldg" => $time['bldg'],
"room" => $time['room'],
"offCampus" => $time['off_campus']
));
// Add the details of the course to the $checker array as a single array object for use when checking if all the details are the same across each time the course meets
array_push($checker, array(
"start" => (int)$time['start'],
"end" => (int)$time['end'],
"bldg" => $time['bldg'],
"room" => $time['room'],
"offCampus" => $time['off_campus']
));
}
$code .= "\r\n";

// Meeting location
if($course['courseNum'] != 'non') {
$bldg = $time['bldg'][$schedule['bldgStyle']];
$code .= "LOCATION:{$bldg}-{$time['room']}\r\n";
/* If the details for the course are equal across each day, then set up a single series that repeats on each day the course runs.
* This compares start time, end time, building, room, and off_campus flag all as one object; see $checker array above.
* If any of the times have different data, then the count will return a number higher than 1 and the code will move to the else statement.
*/
if (count(array_unique($checker, SORT_REGULAR)) === 1) {
$code .= "BEGIN:VEVENT\r\n";
$code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}");
$code .= "\r\n";
$code .= "TZID:America/New_York\r\n";
$code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n";

$startTime = $this->icalFormatTime($time['start']);
$endTime = $this->icalFormatTime($time['end']);

// The start day of the event MUST be offset by its day
// @TODO: Retrieve the timezone from php or the config file
$startDayNum = date("N", $termStart); // get the weekday number of the start day of the term
$day = date("Ymd", $this->firstDayAfterDate($times[0]['day'], $termStart)); //Calculate the start day of the course

// Convert day number to abbreviation (based on iCal spec) for the RRULE
$dayAbbArray = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
$dayAbb = array();
foreach ($times as $time) {
array_push($dayAbb, $dayAbbArray[$time['day']]); //make $dayAbb an array using only the day abbreviations needed for the course. (MO,WE,FR) or (TU,TH) for example
}
$dayList = implode(',', $dayAbb); //Make a string to use in the RRULE

$code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n";
$code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n";
$code .= "RRULE:FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY={$dayList};UNTIL={$termEnd}\r\n"; //add the RRULE that allows the event to repeat across multiple days a week
$code .= "ORGANIZER:RIT\r\n";

// Course name
$code .= "SUMMARY:{$course['title']}";
if($course['courseNum'] != 'non') {
$code .= " ({$course['courseNum']})";
}
$code .= "\r\n";

// Meeting location
if($course['courseNum'] != 'non') {
$bldg = $times[0]['bldg'];
$code .= "LOCATION:{$bldg['code']}-{$times[0]['room']}\r\n";
}

$code .= "END:VEVENT\r\n";
}
else {
//fallback to default behavior of creating a separate event for each time the course runs
// Iterate over all the times to create separate event series for each time
foreach($course['times'] as $time) {
$code .= "BEGIN:VEVENT\r\n";
$code .= "UID:" . md5(uniqid(mt_rand(), true) . " @{$HTTPROOTADDRESS}");
$code .= "\r\n";
$code .= "TZID:America/New_York\r\n";
$code .= "DTSTAMP:" . gmdate('Ymd') . "T" . gmdate("His") . "Z\r\n";

$startTime = $this->icalFormatTime($time['start']);
$endTime = $this->icalFormatTime($time['end']);

// The start day of the event MUST be offset by it's day
// @TODO: Retrieve the timezone from php or the config file
$startDayNum = date("N", $termStart); // get the weekday number of the start day of the term
$day = date("Ymd", $this->firstDayAfterDate($times[0]['day'], $termStart)); //Calculate the start day of the course

$code .= "DTSTART;TZID=America/New_York:{$day}T{$startTime}\r\n";
$code .= "DTEND;TZID=America/New_York:{$day}T{$endTime}\r\n";
$code .= "RRULE:FREQ=WEEKLY;UNTIL={$termEnd}\r\n";
$code .= "ORGANIZER:RIT\r\n";

// Course name
$code .= "SUMMARY:{$course['title']}";
if($course['courseNum'] != 'non') {
$code .= " ({$course['courseNum']})";
}
$code .= "\r\n";

// Meeting location
if($course['courseNum'] != 'non') {
$bldg = $time['bldg'][$schedule['bldgStyle']];
$code .= "LOCATION:{$bldg}-{$time['room']}\r\n";
}

$code .= "END:VEVENT\r\n";
}
}

$code .= "END:VEVENT\r\n";
}
}

$code .= "END:VCALENDAR\r\n";

Expand Down