tags:

views:

221

answers:

2

I'm trying to calculate time blocks from given date range. Here's my attempt:

<?php
class timeRangeCalculator
{
  /**
   * Start time of block
   *
   * @var DateTime
   */
  private $blockStart;

  /**
   * Block duration in seconds
   *
   * @var int
   */
  private $blockDuration;

  /**
   * Start of range
   *
   * @var DateTime
   */
  private $rangeStart;

  /**
   * End of range
   *
   * @var DateTime
   */
  private $rangeEnd;

  /**
   *
   */
  public function __construct()
  {
    if (!class_exists('DateTime'))
    {
      throw new Exception('Built-in DateTime class not found');
    }
  }

  /**
   * Set whole time range
   *
   * @param DateTime $start
   * @param DateTime $end
   */
  public function setRange(DateTime $start, DateTime $end)
  {
    if ($end < $start)
    {
      throw new Exception('End cannot be before start');
    }

    if ($start === $end)
    {
      throw new Exception('Start and end cannot be same');
    }

    $this->rangeStart = $start;
    $this->rangeEnd = $end;

    return true;
  }

  /**
   * Set time block. Ignore year and month.
   *
   * @param DateTime $start
   * @param int $duration duration in seconds
   */
  public function setBlock(DateTime $start, $duration)
  {
    if (!is_int($duration))
    {
      throw new Exception('Duration is not integer');
    }

    if ($duration > 86400)
    {
      throw new Exception('Duration is over one day');
    }

    $this->blockStart = $start;
    $this->blockDuration = $duration;

    return true;
  }

  /**
   * Calculate time blocks from range
   *
   * @return array Returns array of blocks
   */
  public function calculate()
  {
    $result = array();

    $rangeStart = $this->rangeStart;

    $blockStart = $this->blockStart;
    $blockDuration = $this->blockDuration;

    do
    {
      // Reset end range
      $rangeEnd = $this->rangeEnd;

      // Set block's starting date to range's start date
      $blockStart->setDate($rangeStart->format('Y'), $rangeStart->format('m'), $rangeStart->format('d'));

      $blockEnd = clone $blockStart;
      $blockEnd->modify("+$blockDuration seconds");

      $rangeEnd->setDate($blockEnd->format('Y'), $blockEnd->format('m'), $blockEnd->format('d'));


      if ($rangeStart < $blockStart)
      {
        $rangeStart->setTime($blockStart->format('G'), $blockStart->format('i'), $blockStart->format('s'));
      }

      if ($rangeEnd > $blockEnd)
      {
        $rangeEnd->setTime($blockEnd->format('G'), $blockEnd->format('i'), $blockEnd->format('s'));
      }

      if ($rangeStart < $rangeEnd)
      {
        $result[] = array($rangeStart, $rangeEnd);
      }

      // Full day after first iteration
      $rangeStart->setTime(0, 0, 0);

      $rangeStart->modify("+1 day");
    }
    while ($blockEnd <= $this->rangeEnd);

    return $result;
  }

}

I can't quite get what kind of loop I should do in ::calculate().

Test case:

<?php  
$block_start = new DateTime("2000-01-01 22:00:00");
$block_range = 60 * 60 * 8;

$ranges = array(
  array(new DateTime("2009-01-01 00:00:00"), new DateTime("2009-01-02 00:00:00")),
  array(new DateTime("2009-01-01 00:00:00"), new DateTime("2009-01-07 00:00:00"))
);

$trc = new timeRangeCalculator();
$trc->setBlock($block_start, $block_range);

foreach ($ranges as $idx => $rangeInfo)
{
  list($start, $end) = $rangeInfo;

  echo "Range " . $start->format("j.n.Y H:i:s") . " - " . $end->format("j.n.Y H:i:s") . "<br />";

  $trc->setRange($start, $end);
  $result = $trc->calculate();

  if(is_array($result) && !empty($result))
  {
    echo "Has block 22-06: <br />";

    foreach ($result as $block)
    {
      list($bs, $be) = $block;

      echo "* " . $bs->format("j.n.Y H:i:s") . " - " . $be->format("j.n.Y H:i:s") . "<br />";
    }

  }

  echo "<br />";
}

PHP version is 5.2.6 so some DateTime stuff like ::diff() cannot be used.

Edit:

So range is for example

================================

Block is

#######

So blocks from ranges is for example

=====###=====###====....=====###
     ^ ^                     ^ ^
     | |                     | |
     |  - end of block 1     |  - end of block N
      - start of block 1      - start of block N
A: 

Could you please tell us exactly what you want? Why diff() can't be used?

Tareq
DateTime::diff(PHP 5 >= 5.3.0)
w35l3y
So if your block is one day and range is one week then you get seven blocks where start is start of day and end is end of day
raspi
A: 

Ok, this seems to be working correctly:

  /**
   * Calculate time blocks from range
   *
   * @return array Returns array of blocks
   */
  public function calculate()
  {
    $result = array();

    $rangeStart = $this->rangeStart;

    $blockStart = $this->blockStart;
    $blockDuration = $this->blockDuration;

    do
    {
      // Reset end range
      $rangeEnd = clone $this->rangeEnd;

      // Set block's starting date to range's start date
      $blockStart->setDate($rangeStart->format('Y'), $rangeStart->format('m'), $rangeStart->format('d'));

      $blockEnd = clone $blockStart;
      $blockEnd->modify("+$blockDuration seconds");

      // It's not last day of range, so it's full day
      if ($rangeStart->format("Ymd") !== $this->rangeEnd->format("Ymd"))
      {
        $rangeEnd->setDate($blockEnd->format('Y'), $blockEnd->format('m'), $blockEnd->format('d'));
        $rangeEnd->setTime($blockEnd->format('G'), $blockEnd->format('i'), $blockEnd->format('s'));

        if ($rangeEnd > $this->rangeEnd)
        {
          $rangeEnd->setTime(0, 0, 0);
        }
      }

      // It's first day of range, so it's probably partial day
      if ($rangeStart->format("Ymd") === $this->rangeStart->format("Ymd"))
      {
        if ($rangeStart < $blockStart)
        {
          $rangeStart->setTime($blockStart->format('G'), $blockStart->format('i'), $blockStart->format('s'));
        }
      }

      // Add to results
      if ($rangeStart < $rangeEnd)
      {
        $result[] = array(clone $rangeStart, clone $rangeEnd);
      }

      // Full day after first iteration
      $rangeStart->setTime(0, 0, 0);

      $rangeStart->modify("+1 day");
    }
    while ($blockEnd <= $this->rangeEnd);

    return $result;
  }

Edit: No, this one

raspi