1
0
mirror of https://github.com/rlanvin/php-rrule.git synced 2025-04-07 22:53:48 +02:00

getOccurrencesBetween and cache for RSet

This commit is contained in:
rlanvin 2016-03-23 17:05:33 +02:00
parent 65886d99a2
commit d949e96c59
2 changed files with 242 additions and 76 deletions

View File

@ -118,6 +118,12 @@ class RSet implements RRuleInterface
$this->total = null; $this->total = null;
$this->infinite = null; $this->infinite = null;
$this->cache = array(); $this->cache = array();
$this->rlist_heap = null;
$this->rlist_iterator = null;
$this->exlist_heap = null;
$this->exlist_iterator = null;
return $this; return $this;
} }
@ -149,6 +155,15 @@ class RSet implements RRuleInterface
throw new \LogicException('Cannot get all occurrences of an infinite recurrence set.'); throw new \LogicException('Cannot get all occurrences of an infinite recurrence set.');
} }
// cached version already computed
if ( $this->total !== null ) {
$res = array();
foreach ( $this->cache as $occurrence ) {
$res[] = clone $occurrence; // we have to clone because DateTime is not immutable
}
return $res;
}
$res = array(); $res = array();
foreach ( $this as $occurrence ) { foreach ( $this as $occurrence ) {
$res[] = $occurrence; $res[] = $occurrence;
@ -158,7 +173,33 @@ class RSet implements RRuleInterface
public function getOccurrencesBetween($begin, $end) public function getOccurrencesBetween($begin, $end)
{ {
throw new \Exception(__METHOD__.' is unimplemented'); if ( $begin !== null ) {
$begin = RRule::parseDate($begin);
}
if ( $end !== null ) {
$end = RRule::parseDate($end);
}
elseif ( $this->isInfinite() ) {
throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.');
}
$iterator = $this;
if ( $this->total !== null ) {
$iterator = $this->cache;
}
$res = array();
foreach ( $iterator as $occurrence ) {
if ( $begin !== null && $occurrence < $begin ) {
continue;
}
if ( $end !== null && $occurrence > $end ) {
break;
}
$res[] = clone $occurrence;
}
return $res;
} }
public function occursAt($date) public function occursAt($date)
@ -209,16 +250,14 @@ class RSet implements RRuleInterface
public function offsetGet($offset) public function offsetGet($offset)
{ {
// TODO: Cache if ( isset($this->cache[$offset]) ) {
// found in cache
// if ( isset($this->cache[$offset]) ) { return clone $this->cache[$offset];
// // found in cache }
// return $this->cache[$offset]; elseif ( $this->total !== null ) {
// } // cache complete and not found in cache
// elseif ( $this->total !== null ) { return null;
// // cache complete and not found in cache }
// return null;
// }
// not in cache and cache not complete, we have to loop to find it // not in cache and cache not complete, we have to loop to find it
$i = 0; $i = 0;
@ -269,12 +308,16 @@ class RSet implements RRuleInterface
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Private methods // Private methods
private $_rlist = null; // cache variables
private $_rlist_iterator = null; protected $rlist_heap = null;
private $_exlist = null; protected $rlist_iterator = null;
private $_exlist_iterator = null; protected $exlist_heap = null;
protected $exlist_iterator = null;
// local variables for iterate() (see comment in RRule about that)
private $_previous_occurrence = null; private $_previous_occurrence = null;
private $_total = 0; private $_total = 0;
private $_use_cache = 0;
/** /**
* This method will iterate over a bunch of different iterators (rrules and arrays), * This method will iterate over a bunch of different iterators (rrules and arrays),
@ -290,55 +333,89 @@ class RSet implements RRuleInterface
*/ */
protected function iterate($reset = false) protected function iterate($reset = false)
{ {
$rlist = & $this->_rlist; // $rlist = & $this->_rlist;
$rlist_iterator = & $this->_rlist_iterator; // $rlist_iterator = & $this->_rlist_iterator;
$exlist = & $this->_exlist; // $exlist = & $this->_exlist;
$exlist_iterator = & $this->_exlist_iterator; // $exlist_iterator = & $this->_exlist_iterator;
$previous_occurrence = & $this->_previous_occurrence; $previous_occurrence = & $this->_previous_occurrence;
$total = & $this->_total; $total = & $this->_total;
$use_cache = & $this->_use_cache;
if ( $reset ) { if ( $reset ) {
$this->_rlist = $this->_rlist_iterator = null; // $this->_rlist = $this->_rlist_iterator = null;
$this->_exlist = $this->_exlist_iterator = null; // $this->_exlist = $this->_exlist_iterator = null;
$this->_previous_occurrence = null; $this->_previous_occurrence = null;
$this->_total = 0; $this->_total = 0;
$this->_use_cache = true;
reset($this->cache);
} }
if ( $rlist === null ) { // go through the cache first
// rrules + rdate if ( $use_cache ) {
$rlist = new \SplMinHeap(); while ( ($occurrence = current($this->cache)) !== false ) {
$rlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY); // // echo "Cache hit\n";
$rlist_iterator->attachIterator(new \ArrayIterator($this->rdates)); // $dtstart = $occurrence;
foreach ( $this->rrules as $rrule ) { next($this->cache);
$rlist_iterator->attachIterator($rrule); $total += 1;
return clone $occurrence;
} }
$rlist_iterator->rewind(); reset($this->cache);
// now set use_cache to false to skip the all thing on next iteration
// and start filling the cache instead
$use_cache = false;
// if the cache as been used up completely and we now there is nothing else
if ( $total === $this->total ) {
// // echo "Cache used up, nothing else to compute\n";
return null;
}
// // echo "Cache used up with occurrences remaining\n";
// if ( $dtstart ) {
// $dtstart = clone $dtstart; // since DateTime is not immutable, avoid any problem
// // so we skip the last occurrence of the cache
// if ( $this->freq === self::SECONDLY ) {
// $dtstart->modify('+'.$this->interval.'second');
// }
// else {
// $dtstart->modify('+1second');
// }
// }
}
if ( $this->rlist_heap === null ) {
// rrules + rdate
$this->rlist_heap = new \SplMinHeap();
$this->rlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY);
$this->rlist_iterator->attachIterator(new \ArrayIterator($this->rdates));
foreach ( $this->rrules as $rrule ) {
$this->rlist_iterator->attachIterator($rrule);
}
$this->rlist_iterator->rewind();
// exrules + exdate // exrules + exdate
$exlist = new \SplMinHeap(); $this->exlist_heap = new \SplMinHeap();
$exlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY); $this->exlist_iterator = new \MultipleIterator(\MultipleIterator::MIT_NEED_ANY);
$exlist_iterator->attachIterator(new \ArrayIterator($this->exdates)); $this->exlist_iterator->attachIterator(new \ArrayIterator($this->exdates));
foreach ( $this->exrules as $rrule ) { foreach ( $this->exrules as $rrule ) {
$exlist_iterator->attachIterator($rrule); $this->exlist_iterator->attachIterator($rrule);
} }
$exlist_iterator->rewind(); $this->exlist_iterator->rewind();
} }
while ( true ) { while ( true ) {
foreach ( $rlist_iterator->current() as $date ) { foreach ( $this->rlist_iterator->current() as $date ) {
if ( $date !== null ) { if ( $date !== null ) {
$rlist->insert($date); $this->rlist_heap->insert($date);
} }
} }
$rlist_iterator->next(); // advance the iterator for the next call $this->rlist_iterator->next(); // advance the iterator for the next call
if ( $rlist->isEmpty() ) { if ( $this->rlist_heap->isEmpty() ) {
break; // exit the loop to stop the iterator break; // exit the loop to stop the iterator
} }
$occurrence = $rlist->top(); $occurrence = $this->rlist_heap->top();
$rlist->extract(); // remove the occurence from the heap $this->rlist_heap->extract(); // remove the occurence from the heap
if ( $occurrence == $previous_occurrence ) { if ( $occurrence == $previous_occurrence ) {
continue; // skip, was already considered continue; // skip, was already considered
@ -348,26 +425,26 @@ class RSet implements RRuleInterface
// we need to iterate exlist as long as it contains dates lower than occurrence // we need to iterate exlist as long as it contains dates lower than occurrence
// (they will be discarded), and then check if the date is the same // (they will be discarded), and then check if the date is the same
// as occurence (in which case it is discarded) // as occurence (in which case it is discarded)
$exclude = false; $excluded = false;
while ( true ) { while ( true ) {
foreach ( $exlist_iterator->current() as $date ) { foreach ( $this->exlist_iterator->current() as $date ) {
if ( $date !== null ) { if ( $date !== null ) {
$exlist->insert($date); $this->exlist_heap->insert($date);
} }
} }
$exlist_iterator->next(); // advance the iterator for the next call $this->exlist_iterator->next(); // advance the iterator for the next call
if ( $exlist->isEmpty() ) { if ( $this->exlist_heap->isEmpty() ) {
break 1; // break this loop only break 1; // break this loop only
} }
$exdate = $exlist->top(); $exdate = $this->exlist_heap->top();
if ( $exdate < $occurrence ) { if ( $exdate < $occurrence ) {
$exlist->extract(); $this->exlist_heap->extract();
continue; continue;
} }
elseif ( $exdate == $occurrence ) { elseif ( $exdate == $occurrence ) {
$exclude = true; $excluded = true;
break 1; break 1;
} }
else { else {
@ -377,12 +454,13 @@ class RSet implements RRuleInterface
$previous_occurrence = $occurrence; $previous_occurrence = $occurrence;
if ( $exclude ) { if ( $excluded ) {
continue; continue;
} }
$total += 1; $total += 1;
return $occurrence; // = yield $this->cache[] = $occurrence;
return clone $occurrence; // = yield
} }
$this->total = $total; // save total for count cache $this->total = $total; // save total for count cache

View File

@ -5,27 +5,7 @@ use RRule\RRule;
class RSetTest extends PHPUnit_Framework_TestCase class RSetTest extends PHPUnit_Framework_TestCase
{ {
public function testIsInfinite() public function testCombineRRule()
{
$rset = new RSet();
$this->assertFalse($rset->isInfinite());
$this->assertTrue($rset->isFinite());
$rset->addRRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 10
));
$this->assertFalse($rset->isInfinite());
$this->assertTrue($rset->isFinite());
$rset->addRRule(array(
'FREQ' => 'YEARLY'
));
$this->assertTrue($rset->isInfinite());
$this->assertFalse($rset->isFinite());
}
public function testAddRRule()
{ {
$rset = new RSet(); $rset = new RSet();
$rset->addRRule(array( $rset->addRRule(array(
@ -46,9 +26,12 @@ class RSetTest extends PHPUnit_Framework_TestCase
date_create('1997-09-04 09:00'), date_create('1997-09-04 09:00'),
date_create('1997-09-09 09:00') date_create('1997-09-09 09:00')
), $rset->getOccurrences()); ), $rset->getOccurrences());
$this->assertEquals(date_create('1997-09-04 09:00'),$rset[1]);
$this->assertEquals(array(date_create('1997-09-04 09:00')),$rset->getOccurrencesBetween('1997-09-04 00:00', '1997-09-05 00:00'));
} }
public function testAddDate() public function testCombineRRuleAndDate()
{ {
$rset = new RSet(); $rset = new RSet();
$rset->addRRule(array( $rset->addRRule(array(
@ -64,9 +47,12 @@ class RSetTest extends PHPUnit_Framework_TestCase
date_create('1997-09-04 09:00'), date_create('1997-09-04 09:00'),
date_create('1997-09-09 09:00') date_create('1997-09-09 09:00')
), $rset->getOccurrences()); ), $rset->getOccurrences());
$this->assertEquals(date_create('1997-09-04 09:00'),$rset[1]);
$this->assertEquals(array(date_create('1997-09-04 09:00')),$rset->getOccurrencesBetween('1997-09-04 00:00', '1997-09-05 00:00'));
} }
public function testAddExRule() public function testCombineRRuleAndExRule()
{ {
$rset = new RSet(); $rset = new RSet();
$rset->addRRule(array( $rset->addRRule(array(
@ -86,9 +72,12 @@ class RSetTest extends PHPUnit_Framework_TestCase
date_create('1997-09-09 09:00'), date_create('1997-09-09 09:00'),
date_create('1997-09-16 09:00') date_create('1997-09-16 09:00')
), $rset->getOccurrences()); ), $rset->getOccurrences());
$this->assertEquals(date_create('1997-09-09 09:00'),$rset[1]);
$this->assertEquals(array(date_create('1997-09-16 09:00')),$rset->getOccurrencesBetween('1997-09-16 00:00', '1997-09-17 00:00'));
} }
public function testAddExDate() public function testCombineRRuleAndExDate()
{ {
$rset = new RSet(); $rset = new RSet();
$rset->addRRule(array( $rset->addRRule(array(
@ -106,13 +95,113 @@ class RSetTest extends PHPUnit_Framework_TestCase
date_create('1997-09-09 09:00'), date_create('1997-09-09 09:00'),
date_create('1997-09-16 09:00') date_create('1997-09-16 09:00')
), $rset->getOccurrences()); ), $rset->getOccurrences());
$this->assertEquals(date_create('1997-09-09 09:00'),$rset[1]);
$this->assertEquals(array(date_create('1997-09-16 09:00')),$rset->getOccurrencesBetween('1997-09-16 00:00', '1997-09-17 00:00'));
} }
public function testAddDateAndExRule() public function testCombineEverything()
{ {
// TODO // TODO
} }
///////////////////////////////////////////////////////////////////////////////
// Other tests
public function testIsInfinite()
{
$rset = new RSet();
$this->assertFalse($rset->isInfinite());
$this->assertTrue($rset->isFinite());
$rset->addRRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 10
));
$this->assertFalse($rset->isInfinite());
$this->assertTrue($rset->isFinite());
$rset->addRRule(array(
'FREQ' => 'YEARLY'
));
$this->assertTrue($rset->isInfinite());
$this->assertFalse($rset->isFinite());
}
public function testModifyResetCache()
{
$rset = new RSet();
$rset->addRRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 6,
'BYDAY' => 'TU,TH',
'DTSTART' => date_create('1997-09-02 09:00')
));
$this->assertEquals(array(
date_create('1997-09-02 09:00'),
date_create('1997-09-04 09:00'),
date_create('1997-09-09 09:00'),
date_create('1997-09-11 09:00'),
date_create('1997-09-16 09:00'),
date_create('1997-09-18 09:00')
), $rset->getOccurrences());
$r = new ReflectionObject($rset);
$cache = $r->getProperty('cache');
$cache->setAccessible('true');
$this->assertNotEmpty($cache->getValue($rset), 'Cache is not empty');
$rset->addExRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 3,
'BYDAY' => 'TH',
'DTSTART' => date_create('1997-09-02 09:00')
));
$this->assertEmpty($cache->getValue($rset), 'Cache has been emptied by addExRule');
$this->assertEquals(array(
date_create('1997-09-02 09:00'),
date_create('1997-09-09 09:00'),
date_create('1997-09-16 09:00')
), $rset->getOccurrences(), 'Iteration works');
}
public function testPartialCache()
{
$rset = new RSet();
$rset->addRRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 6,
'BYDAY' => 'TU,TH',
'DTSTART' => date_create('1997-09-02 09:00')
));
$rset->addExRule(array(
'FREQ' => 'YEARLY',
'COUNT' => 3,
'BYDAY' => 'TH',
'DTSTART' => date_create('1997-09-02 09:00')
));
foreach ( $rset as $occurrence ) {
$this->assertEquals(date_create('1997-09-02 09:00'), $occurrence);
break;
}
$r = new ReflectionObject($rset);
$cache = $r->getProperty('cache');
$cache->setAccessible('true');
$this->assertNotEmpty($cache->getValue($rset), 'Cache is not empty (partially filled)');
$this->assertEquals(date_create('1997-09-02 09:00'), $rset[0], 'Partial cache is returned');
$this->assertEquals(date_create('1997-09-09 09:00'), $rset[1], 'Next occurrence is calculated correctly');
$this->assertEquals(array(
date_create('1997-09-02 09:00'),
date_create('1997-09-09 09:00'),
date_create('1997-09-16 09:00')
), $rset->getOccurrences(), 'Iteration works');
}
public function testCountable() public function testCountable()
{ {
$rset = new RSet(); $rset = new RSet();
@ -161,7 +250,6 @@ class RSetTest extends PHPUnit_Framework_TestCase
$rset->addExdate('1997-09-11 09:00:00'); $rset->addExdate('1997-09-11 09:00:00');
$rset->addExdate('1997-09-18 09:00:00'); $rset->addExdate('1997-09-18 09:00:00');
// var_dump($rset->getOccurrences());
$this->assertEquals(date_create('1997-09-02 09:00:00'), $rset[0]); $this->assertEquals(date_create('1997-09-02 09:00:00'), $rset[0]);
$this->assertEquals(date_create('1997-09-09 09:00:00'), $rset[1]); $this->assertEquals(date_create('1997-09-09 09:00:00'), $rset[1]);
$this->assertEquals(date_create('1997-09-16 09:00:00'), $rset[2]); $this->assertEquals(date_create('1997-09-16 09:00:00'), $rset[2]);