# HG changeset patch # User Nathan Phillip Brink # Date 2012-04-22 12:01:16 # Node ID a72ad365c4f07d4832ae8f09e739c7148455f727 # Parent 146ce01816f17f0296b641f1c17b5184b23f611d Add support to the collision-detection engine for half-semester sections. Breaks the calendar view. diff --git a/inc/admin.inc b/inc/admin.inc --- a/inc/admin.inc +++ b/inc/admin.inc @@ -554,6 +554,37 @@ 3'; $n += assert_equal('csv_partial', school_crawl_csv_parse($csv_partial), array(array('1', '2'))); $n += assert_equal('csv_partial_buffer', $csv_partial, '3'); + $section_meeting_a = new SectionMeeting('mwf', '1900', '1950', NULL, 'lecture', NULL, 1335063574 /* 2012-04-22 (sat) */, 1348282798 /* bit after 2012-09-21 (fri) */); + $n += assert_equal('date_start_get+mon', $section_meeting_a->date_start_get(), 1335207600); + $n += assert_equal('date_end_get+fri', $section_meeting_a->date_end_get(), 1348257000); + + $section_meeting_b = new SectionMeeting('mwf', '1900', '1950', NULL, 'lecture', NULL, 1335495574 /* 2012-04-27 (thur) */, 1348109998 /* bit after 2012-09-19 (wed) */); + $n += assert_equal('date_start_get+thur', $section_meeting_b->date_start_get(), 1335553200); + $n += assert_equal('date_end_get+wed', $section_meeting_b->date_end_get(), 1348084200); + + /* The two section meetings above should overlap */ + $n += assert_equal('section_meeting_collide', $section_meeting_a->conflictsWith($section_meeting_b), TRUE); + + /* + * A third section meeting has the same time of day but starts the + * day after secftion_meeting_b: + */ + $section_meeting_c = new SectionMeeting('mwf', '1900', '1950', NULL, 'lecture', NULL, 1348109998 /* bit after 2012-09-19 (wed) */, 1354406400 /* bit after 2012-12-01 (wed) */); + $n += assert_equal('section_meeting_collide_a', $section_meeting_a->conflictsWith($section_meeting_c), TRUE); + $n += assert_equal('section_meeting_no_collide_b', $section_meeting_b->conflictsWith($section_meeting_c), FALSE); + + /* + * If a section meeting doesn't specify an absolute start/end time, + * it must always conflict. + */ + $section_meeting_d = new SectionMeeting('mwf', '1900', '1950'); + $n += assert_equal('section_meeting_collide_d_a', $section_meeting_d->conflictsWith($section_meeting_a), TRUE); + $n += assert_equal('section_meeting_collide_a_d', $section_meeting_a->conflictsWith($section_meeting_d), TRUE); + $n += assert_equal('section_meeting_collide_d_b', $section_meeting_d->conflictsWith($section_meeting_b), TRUE); + $n += assert_equal('section_meeting_collide_b_d', $section_meeting_b->conflictsWith($section_meeting_d), TRUE); + $n += assert_equal('section_meeting_collide_d_c', $section_meeting_d->conflictsWith($section_meeting_c), TRUE); + $n += assert_equal('section_meeting_collide_c_b', $section_meeting_c->conflictsWith($section_meeting_d), TRUE); + return $n; } diff --git a/inc/class.section_meeting.inc b/inc/class.section_meeting.inc --- a/inc/class.section_meeting.inc +++ b/inc/class.section_meeting.inc @@ -1,4 +1,4 @@ -_time_first_meeting_start); + unset($this->_time_first_meeting_end); + } + + /** + * \brief * Take a days of week string and store it into our $days of week array. * * \param $days_str @@ -105,6 +128,8 @@ class SectionMeeting $days_str_strlen = strlen($days_str); for ($i = 0; $i < $days_str_strlen; $i ++) $this->days[self::day_atoi($days_str[$i])] = TRUE; + + $this->_cache_reset(); } /** @@ -166,6 +191,27 @@ class SectionMeeting } /** + * \brief + * Get a string representing the days of week during which this meeting meets. + */ + public function days_get() + { + static $daymap = array(0 => 'm', 1 => 't', 2 => 'w', 3 => 'h', 4 => 'f', 5 => 's', 6 => 'u'); + static $dayorder_reverse = array(6 => TRUE); + $days = ''; + for ($day = 0; $day < 7; $day ++) + if ($this->getDay($day)) + { + if (empty($dayorder_reverse[$day])) + $days .= $daymap[$day]; + else + $days = $daymap[$day] . $days; + } + return $days; + } + + + /** * \return * This SectionMeeting's location or NULL if none is defined. */ @@ -208,22 +254,131 @@ class SectionMeeting /** * \brief - * Return the unix timestamp of a time prior to the first section - * meeting or NULL if unknown. + * Return the unix timestamp of the exact time prior of the first + * section meeting or NULL if unknown. */ public function date_start_get() { - return empty($this->date_start) ? NULL : $this->date_start; + static $day_to_real_day = array('u' => 0, 'm' => 1, 't' => 2, 'w' => 3, 'h' => 4, 'f' => 5, 's' => 6); + + if (empty($this->_time_first_meeting_start)) + { + if (empty($this->date_start)) + return NULL; + + /* + * For now, just assume UTC. Otherwise, we'd need a handle to + * the school and for each school to specify its timezone. Not + * a bad idea, but even if the school has a timezone + * associated with it (which it will someday soon...), this is + * more efficient. + */ + $hour = substr($this->getStartTime(), 0, 2); + $minute = substr($this->getStartTime(), 2, 2); + + /* + * This is the dirty part. Find the first instance of this + * section_meeting's day of week and convert that to a day of + * month for gmmktime() call below. Assumes that a day is at + * least 12 hours long (there are 23 and 25 hours days near + * daylight saving changes, so there _are_ issues with + * assuming 24 hours). + */ + + /* Search out the first day... */ + $earliest_day = -1; + $earliest_day_time = 0; + $days_of_week = $this->days_get(); + for ($i = 0; $i < strlen($days_of_week); $i ++) + { + /* Find the first occurence of the currently tested day after $this->date_start */ + $day_of_week_sought = $day_to_real_day[$days_of_week[$i]]; + $day_of_week_time = $this->date_start - 12 * 60*60; + do + { + $day_of_week = gmdate('w', $day_of_week_time); + $day_of_week_time += 12 * 60*60; + } + while ($day_of_week != $day_of_week_sought); + + /* Find exact time of this meeting on that day */ + $day_of_week_time = gmmktime($hour, $minute, 0, + gmdate('n', $day_of_week_time), + gmdate('j', $day_of_week_time), + gmdate('Y', $day_of_week_time)); + + if ($earliest_day == -1 + || $day_of_week_time < $earliest_day_time) + { + $earliest_day = $i; + $earliest_day_time = $day_of_week_time; + } + } + + $this->_time_first_meeting_start = $earliest_day_time; + } + + return $this->_time_first_meeting_start; } /** * \brief - * Return the unix timestamp of a time after the last section - * meeting or NULL if unknown. + * Return the unix timestamp of the exact time at which the the + * last section meeting stops or NULL if unknown. + * + * This is implemented in mirror to start_date_get(). Refer to that + * function for rationale and comments. */ public function date_end_get() { - return empty($this->date_end) ? NULL : $this->date_end; + static $day_to_real_day = array('u' => 0, 'm' => 1, 't' => 2, 'w' => 3, 'h' => 4, 'f' => 5, 's' => 6); + + if (empty($this->_time_last_meeting_end)) + { + if (empty($this->date_end)) + return NULL; + + /* Assume UTC */ + $hour = substr($this->getEndTime(), 0, 2); + $minute = substr($this->getEndTime(), 2, 2); + + /* Find last meeting time */ + + /* Search out the last day of week... */ + $latest_day = -1; + $latest_day_time = 0; + $days_of_week = $this->days_get(); + for ($i = 0; $i < strlen($days_of_week); $i ++) + { + /* Find last occurence of the currently tested day before $this->date_end */ + $day_of_week_sought = $day_to_real_day[$days_of_week[$i]]; + $day_of_week_time = $this->date_end + 12 * 60*60; + $day_of_week = ''; + do + { + $day_of_week = gmdate('w', $day_of_week_time); + $day_of_week_time -= 12 * 60*60; + } + while ($day_of_week != $day_of_week_sought); + + /* Find exact time this meeting ends on that day */ + $day_of_week_time = gmmktime($hour, $minute, 0, + gmdate('n', $day_of_week_time), + gmdate('j', $day_of_week_time), + gmdate('Y', $day_of_week_time)); + + if ($latest_day == -1 + || $day_of_week_time > $latest_day_time) + { + $latest_day = $i; + $latest_day_time = $day_of_week_time; + } + } + + $this->_time_last_meeting_end = $latest_day_time; + } + + return $this->_time_last_meeting_end; } /** @@ -241,9 +396,15 @@ class SectionMeeting * The two sections meetings can't conflict if the start/end times * don't overlap. Also, use >= or <= here so that one can say ``I * have gym from 10 through 11 and then latin from 11 though 12''. + * + * They also can't conflict if the unix timestamps of the first + * and last meetings indicate that the sections are from different + * parts of the semester. */ if ($this->getStartTime() >= $that->getEndTime() - || $this->getEndTime() <= $that->getStartTime()) + || $this->getEndTime() <= $that->getStartTime() + || $this->date_start_get() >= ($that_end = $that->date_end_get()) && $that_end !== NULL + || ($this_end = $this->date_end_get()) <= $that->date_start_get() && $this_end !== NULL) { return FALSE; }