Changeset - a72ad365c4f0
[Not reviewed]
default
0 2 0
Nathan Brink (binki) - 13 years ago 2012-04-22 12:01:16
ohnobinki@ohnopublishing.net
Add support to the collision-detection engine for half-semester sections. Breaks the calendar view.
2 files changed with 200 insertions and 8 deletions:
0 comments (0 inline, 0 general)
inc/admin.inc
Show inline comments
 
@@ -545,24 +545,55 @@ 4';
 
  $n += assert_equal('csv', school_crawl_csv_parse($csv, array('eof' => TRUE)), $csv_parsed);
 
  $n += assert_equal('csv_buffer', $csv, '');
 

	
 
  $csv_partial = '1,2
 
3';
 
  /*
 
   * Check partial parsing support; give a situation where we
 
   * supposedly don't have eof.
 
   */
 
  $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;
 
}
 

	
 
/**
 
 * \brief
 
 *   A reimplementation of a standard testsuite utility.
 
 *
 
 * \return
 
 *   TRUE if the test failed.
 
 */
 
function assert_equal($name, $a, $b)
 
{
inc/class.section_meeting.inc
Show inline comments
 
<?php /* -*- mode: php; -*- */
 
<?php /* -*- mode: php; indent-tabs-mode: nil; -*- */
 
/*
 
 * Copyright 2010 Nathan Gelderloos, Ethan Zonca, Nathan Phillip Brink
 
 *
 
 * This file is part of SlatePermutate.
 
 *
 
 * SlatePermutate is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * SlatePermutate is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
@@ -36,24 +36,37 @@
 
class SectionMeeting
 
{
 
  private $date_start;
 
  private $time_start;
 
  private $date_end;
 
  private $time_end;
 
  private $days;
 
  private $location;
 
  private $instructor;
 

	
 
  /**
 
   * \brief
 
   *   Cache some calculations. The timestamp of the first meeting's
 
   *   absolute start time.
 
   */
 
  private $_time_first_meeting_start;
 
  /**
 
   * \brief
 
   *   Cache some calculations. The timestamp of the exact last
 
   *   meeting's end time.
 
   */
 
  private $_time_last_meeting_end;
 

	
 
  /**
 
   * \brief
 
   *   Construct a SectionMeeting.
 
   *
 
   * \param $days
 
   *   A string of single-char day upon which a section meets. Sunday
 
   *   is represented with 'u', Monday with 'm', Tuesday with 't',
 
   *   Wednesday with 'w', Thursday with 'h', Friday with 'f', and
 
   *   Saturday with 's'.
 
   * \param $time_start
 
   *   The time of day when the section meeting starts. Use
 
   *   school_crawl_time_format() or ensure that the time is formatted
 
   *   in 24-hour, 0-padded, 4-digit form (HHMM).
 
   * \param $time_end
 
@@ -82,38 +95,50 @@ class SectionMeeting
 
    $this->time_start = $time_start;
 
    $this->date_end = empty($date_end) ? NULL : (int)$date_end;
 
    $this->time_end = $time_end;
 

	
 
    $this->location = $location;
 

	
 
    $this->type = $type;
 
    $this->instructor = $instructor;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Mark certain cached values as needing to be recalculated.
 
   */
 
  private function _cache_reset()
 
  {
 
    unset($this->_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
 
   *   The days of the week in a string format. One char per
 
   *   day. Sun-Sat is represented with 'u', 'm', 't', 'w', 'h', 'f',
 
   *   's'.
 
   */
 
  private function days_set($days_str)
 
  {
 
    $this->days = array(0 => FALSE, 1 => FALSE, 2 => FALSE, 3 => FALSE, 4 => FALSE, 5 => FALSE, 6 => FALSE);
 

	
 
    $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();
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Convert a day letter to a day numeral.
 
   *
 
   * Works fine if you give the numeral as well.
 
   */
 
  private static function day_atoi($day_c)
 
  {
 
    static $day_atoi = array(
 
      'm' => 0, 't' => 1, 'w' => 2, 'h' => 3, 'f' => 4, 's' => 5, 'u' => 6,
 
@@ -157,24 +182,45 @@ class SectionMeeting
 
   * \param $day
 
   *   The letter or numeral of the day to retrieve.
 
   * \return
 
   *   TRUE if this section meeting meets on that day. FALSE
 
   *   otherwise.
 
   */
 
  public function getDay($day)
 
  {
 
    return $this->days[self::day_atoi($day)];
 
  }
 

	
 
  /**
 
   * \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.
 
   */
 
  public function getLocation()
 
  {
 
    return $this->location;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Get the start_time of this meeting in slate_permutate's internal format.
 
   */
 
@@ -199,60 +245,175 @@ class SectionMeeting
 
   * SectionMeeting().
 
   *
 
   * \return
 
   *   A string indicating the type of section meeting.
 
   */
 
  public function type_get()
 
  {
 
    return $this->type;
 
  }
 

	
 
  /**
 
   * \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;
 
  }
 

	
 
  /**
 
   * \brief
 
   *   Check if this section conflicts with the given section.
 
   *
 
   * \param $that
 
   *   The other section for which I should check for conflicts.
 
   * \return
 
   *   TRUE if there is a conflict, FALSE otherwise.
 
   */
 
  public function conflictsWith(SectionMeeting $that)
 
  {
 
    /*
 
     * 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;
 
      }
 

	
 
    /*
 
     * Now we know that the sections meetings overlap in start/end
 
     * times. But if they don't both meet on the same day at least
 
     * once, they don't conflict.
 
     */
 
    for ($day = 0; $day < 7; $day ++)
 
      {
 
	if ($this->getDay($day) && $that->getDay($day))
0 comments (0 inline, 0 general)