1
0
mirror of https://github.com/rlanvin/php-rrule.git synced 2025-02-22 11:54:14 +01:00

Replacing static variables by class variables

Static variables in the class method caused a problem when
iterating multiple RRule object at the same time.
This commit is contained in:
rlanvin 2016-03-15 23:30:50 +02:00
parent d735154d6f
commit 9a5cbdf93e

View File

@ -158,6 +158,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
protected $total = null; protected $total = null;
protected $cache = array(); protected $cache = array();
///////////////////////////////////////////////////////////////////////////////
// Public interface // Public interface
/** /**
@ -885,6 +886,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
return false; return false;
} }
///////////////////////////////////////////////////////////////////////////////
// Iterator interface // Iterator interface
// Note: if cache is complete, we could probably avoid completely calling iterate() // Note: if cache is complete, we could probably avoid completely calling iterate()
// and instead iterate directly on the $this->cache array // and instead iterate directly on the $this->cache array
@ -919,6 +921,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
return $this->current !== null; return $this->current !== null;
} }
///////////////////////////////////////////////////////////////////////////////
// ArrayAccess interface // ArrayAccess interface
public function offsetExists($offset) public function offsetExists($offset)
@ -961,10 +964,11 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
throw new \LogicException('Unsetting a Date in a RRule is not supported'); throw new \LogicException('Unsetting a Date in a RRule is not supported');
} }
///////////////////////////////////////////////////////////////////////////////
// Countable interface // Countable interface
/** /**
* Returns the number of recurrences in this set. It will have go * Returns the number of occurrences in this rule. It will have go
* through the whole recurrence, if this hasn't been done before, which * through the whole recurrence, if this hasn't been done before, which
* introduces a performance penality. * introduces a performance penality.
* @return int * @return int
@ -982,6 +986,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
return $this->total; return $this->total;
} }
///////////////////////////////////////////////////////////////////////////////
// private methods // private methods
// where all the magic happens // where all the magic happens
@ -1270,6 +1275,33 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
} }
} }
/**
* Variables for iterate() method, that will persist to allow iterate()
* to resume where it stopped. For PHP >= 5.5, these would be local variables
* inside a generator method using yield. However since we are compatible with
* PHP 5.3 and 5.4, they have to be implemented this way.
*
* The original implementation used static local variables inside the class
* method, which I think was cleaner scope-wise, but sadly this didn't work
* when multiple instances of RRule existed and are iterated at the same time
* (such as in a ruleset)
*
* DO NOT USE OUTSIDE OF iterate()
*/
private $_year = null;
private $_month = null;
private $_day = null;
private $_hour = null;
private $_minute = null;
private $_second = null;
private $_dayset = null;
private $_masks = null;
private $_timeset = null;
private $_dtstart = null;
private $_total = 0;
private $_use_cache = true;
/** /**
* This is the main method, where all of the magic happens. * This is the main method, where all of the magic happens.
* *
@ -1320,100 +1352,93 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
*/ */
protected function iterate($reset = false) protected function iterate($reset = false)
{ {
// these are the static variables, i.e. the variables that persists
// at every call of the method (to emulate a generator)
static $year = null, $month = null, $day = null;
static $hour = null, $minute = null, $second = null;
static $dayset = null, $masks = null, $timeset = null;
static $dtstart = null, $total = 0, $use_cache = true;
if ( $reset ) { if ( $reset ) {
$year = $month = $day = null; $this->_year = $this->_month = $this->_day = null;
$hour = $minute = $second = null; $this->_hour = $this->_minute = $this->_second = null;
$dayset = $masks = $timeset = null; $this->_dayset = $this->_masks = $this->_timeset = null;
$dtstart = null; $this->_dtstart = null;
$total = 0; $this->_total = 0;
$use_cache = true; $this->_use_cache = true;
reset($this->cache); reset($this->cache);
} }
// go through the cache first // go through the cache first
if ( $use_cache ) { if ( $this->_use_cache ) {
while ( ($occurrence = current($this->cache)) !== false ) { while ( ($occurrence = current($this->cache)) !== false ) {
// echo "Cache hit\n"; // echo "Cache hit\n";
$dtstart = $occurrence; $this->_dtstart = $occurrence;
next($this->cache); next($this->cache);
$total += 1; $this->_total += 1;
return $occurrence; return $occurrence;
} }
reset($this->cache); reset($this->cache);
// now set use_cache to false to skip the all thing on next iteration // now set use_cache to false to skip the all thing on next iteration
// and start filling the cache instead // and start filling the cache instead
$use_cache = false; $this->_use_cache = false;
// if the cache as been used up completely and we now there is nothing else // if the cache as been used up completely and we now there is nothing else
if ( $total === $this->total ) { if ( $this->_total === $this->total ) {
// echo "Cache used up, nothing else to compute\n"; // echo "Cache used up, nothing else to compute\n";
return null; return null;
} }
// echo "Cache used up with occurrences remaining\n"; // echo "Cache used up with occurrences remaining\n";
if ( $dtstart ) { if ( $this->_dtstart ) {
// so we skip the last occurrence of the cache // so we skip the last occurrence of the cache
if ( $this->freq === self::SECONDLY ) { if ( $this->freq === self::SECONDLY ) {
$dtstart->modify('+'.$this->interval.'second'); $this->_dtstart->modify('+'.$this->interval.'second');
} }
else { else {
$dtstart->modify('+1second'); $this->_dtstart->modify('+1second');
} }
} }
} }
// stop once $total has reached COUNT // stop once $this->_total has reached COUNT
if ( $this->count && $total >= $this->count ) { if ( $this->count && $this->_total >= $this->count ) {
$this->total = $total; $this->total = $this->_total;
return null; return null;
} }
if ( $dtstart === null ) { if ( $this->_dtstart === null ) {
$dtstart = clone $this->dtstart; $this->_dtstart = clone $this->dtstart;
} }
if ( $year === null ) { if ( $this->_year === null ) {
if ( $this->freq === self::WEEKLY ) { if ( $this->freq === self::WEEKLY ) {
// we align the start date to the WKST, so we can then // we align the start date to the WKST, so we can then
// simply loop by adding +7 days. The Python lib does some // simply loop by adding +7 days. The Python lib does some
// calculation magic at the end of the loop (when incrementing) // calculation magic at the end of the loop (when incrementing)
// to realign on first pass. // to realign on first pass.
$tmp = clone $dtstart; $tmp = clone $this->_dtstart;
$tmp->modify('-'.pymod($this->dtstart->format('N') - $this->wkst,7).'days'); $tmp->modify('-'.pymod($this->dtstart->format('N') - $this->wkst,7).'days');
list($year,$month,$day,$hour,$minute,$second) = explode(' ',$tmp->format('Y n j G i s')); list($this->_year,$this->_month,$this->_day,$this->_hour,$this->_minute,$this->_second) = explode(' ',$tmp->format('Y n j G i s'));
unset($tmp); unset($tmp);
} }
else { else {
list($year,$month,$day,$hour,$minute,$second) = explode(' ',$dtstart->format('Y n j G i s')); list($this->_year,$this->_month,$this->_day,$this->_hour,$this->_minute,$this->_second) = explode(' ',$this->_dtstart->format('Y n j G i s'));
} }
// remove leading zeros // remove leading zeros
$minute = (int) $minute; $this->_minute = (int) $this->_minute;
$second = (int) $second; $this->_second = (int) $this->_second;
} }
// we initialize the timeset // we initialize the timeset
if ( $timeset == null ) { if ( $this->_timeset == null ) {
if ( $this->freq < self::HOURLY ) { if ( $this->freq < self::HOURLY ) {
// daily, weekly, monthly or yearly // daily, weekly, monthly or yearly
// we don't need to calculate a new timeset // we don't need to calculate a new timeset
$timeset = $this->timeset; $this->_timeset = $this->timeset;
} }
else { else {
// initialize empty if it's not going to occurs on the first iteration // initialize empty if it's not going to occurs on the first iteration
if ( if (
($this->freq >= self::HOURLY && $this->byhour && ! in_array($hour, $this->byhour)) ($this->freq >= self::HOURLY && $this->byhour && ! in_array($this->_hour, $this->byhour))
|| ($this->freq >= self::MINUTELY && $this->byminute && ! in_array($minute, $this->byminute)) || ($this->freq >= self::MINUTELY && $this->byminute && ! in_array($this->_minute, $this->byminute))
|| ($this->freq >= self::SECONDLY && $this->bysecond && ! in_array($second, $this->bysecond)) || ($this->freq >= self::SECONDLY && $this->bysecond && ! in_array($this->_second, $this->bysecond))
) { ) {
$timeset = array(); $this->_timeset = array();
} }
else { else {
$timeset = $this->getTimeSet($hour, $minute, $second); $this->_timeset = $this->getTimeSet($this->_hour, $this->_minute, $this->_second);
} }
} }
} }
@ -1424,97 +1449,97 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
// 1. get an array of all days in the next interval (day, month, week, etc.) // 1. get an array of all days in the next interval (day, month, week, etc.)
// we filter out from this array all days that do not match the BYXXX conditions // we filter out from this array all days that do not match the BYXXX conditions
// to speed things up, we use days of the year (day numbers) instead of date // to speed things up, we use days of the year (day numbers) instead of date
if ( $dayset === null ) { if ( $this->_dayset === null ) {
// rebuild the various masks and converters // rebuild the various masks and converters
// these arrays will allow fast date operations // these arrays will allow fast date operations
// without relying on date() methods // without relying on date() methods
if ( empty($masks) || $masks['year'] != $year || $masks['month'] != $month ) { if ( empty($this->_masks) || $this->_masks['year'] != $this->_year || $this->_masks['month'] != $this->_month ) {
$masks = array('year' => '','month'=>''); $this->_masks = array('year' => '','month'=>'');
// only if year has changed // only if year has changed
if ( $masks['year'] != $year ) { if ( $this->_masks['year'] != $this->_year ) {
$masks['leap_year'] = is_leap_year($year); $this->_masks['leap_year'] = is_leap_year($this->_year);
$masks['year_len'] = 365 + (int) $masks['leap_year']; $this->_masks['year_len'] = 365 + (int) $this->_masks['leap_year'];
$masks['next_year_len'] = 365 + is_leap_year($year + 1); $this->_masks['next_year_len'] = 365 + is_leap_year($this->_year + 1);
$masks['weekday_of_1st_yearday'] = date_create($year."-01-01 00:00:00")->format('N'); $this->_masks['weekday_of_1st_yearday'] = date_create($this->_year."-01-01 00:00:00")->format('N');
$masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $masks['weekday_of_1st_yearday']-1); $this->_masks['yearday_to_weekday'] = array_slice(self::$WEEKDAY_MASK, $this->_masks['weekday_of_1st_yearday']-1);
if ( $masks['leap_year'] ) { if ( $this->_masks['leap_year'] ) {
$masks['yearday_to_month'] = self::$MONTH_MASK_366; $this->_masks['yearday_to_month'] = self::$MONTH_MASK_366;
$masks['yearday_to_monthday'] = self::$MONTHDAY_MASK_366; $this->_masks['yearday_to_monthday'] = self::$MONTHDAY_MASK_366;
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366; $this->_masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK_366;
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366; $this->_masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH_366;
} }
else { else {
$masks['yearday_to_month'] = self::$MONTH_MASK; $this->_masks['yearday_to_month'] = self::$MONTH_MASK;
$masks['yearday_to_monthday'] = self::$MONTHDAY_MASK; $this->_masks['yearday_to_monthday'] = self::$MONTHDAY_MASK;
$masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK; $this->_masks['yearday_to_monthday_negative'] = self::$NEGATIVE_MONTHDAY_MASK;
$masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH; $this->_masks['last_day_of_month'] = self::$LAST_DAY_OF_MONTH;
} }
if ( $this->byweekno ) { if ( $this->byweekno ) {
$this->buildWeeknoMask($year, $month, $day, $masks); $this->buildWeeknoMask($this->_year, $this->_month, $this->_day, $this->_masks);
} }
} }
// everytime month or year changes // everytime month or year changes
if ( $this->byweekday_nth ) { if ( $this->byweekday_nth ) {
$this->buildNthWeekdayMask($year, $month, $day, $masks); $this->buildNthWeekdayMask($this->_year, $this->_month, $this->_day, $this->_masks);
} }
$masks['year'] = $year; $this->_masks['year'] = $this->_year;
$masks['month'] = $month; $this->_masks['month'] = $this->_month;
} }
// calculate the current set // calculate the current set
$dayset = $this->getDaySet($year, $month, $day, $masks); $this->_dayset = $this->getDaySet($this->_year, $this->_month, $this->_day, $this->_masks);
$filtered_set = array(); $filtered_set = array();
foreach ( $dayset as $yearday ) { foreach ( $this->_dayset as $this->_yearday ) {
if ( $this->bymonth && ! in_array($masks['yearday_to_month'][$yearday], $this->bymonth) ) { if ( $this->bymonth && ! in_array($this->_masks['yearday_to_month'][$this->_yearday], $this->bymonth) ) {
continue; continue;
} }
if ( $this->byweekno && ! isset($masks['yearday_is_in_weekno'][$yearday]) ) { if ( $this->byweekno && ! isset($this->_masks['yearday_is_in_weekno'][$this->_yearday]) ) {
continue; continue;
} }
if ( $this->byyearday ) { if ( $this->byyearday ) {
if ( $yearday < $masks['year_len'] ) { if ( $this->_yearday < $this->_masks['year_len'] ) {
if ( ! in_array($yearday + 1, $this->byyearday) && ! in_array(- $masks['year_len'] + $yearday,$this->byyearday) ) { if ( ! in_array($this->_yearday + 1, $this->byyearday) && ! in_array(- $this->_masks['year_len'] + $this->_yearday,$this->byyearday) ) {
continue; continue;
} }
} }
else { // if ( ($yearday >= $masks['year_len'] else { // if ( ($this->_yearday >= $this->_masks['year_len']
if ( ! in_array($yearday + 1 - $masks['year_len'], $this->byyearday) && ! in_array(- $masks['next_year_len'] + $yearday - $mask['year_len'], $this->byyearday) ) { if ( ! in_array($this->_yearday + 1 - $this->_masks['year_len'], $this->byyearday) && ! in_array(- $this->_masks['next_year_len'] + $this->_yearday - $mask['year_len'], $this->byyearday) ) {
continue; continue;
} }
} }
} }
if ( ($this->bymonthday || $this->bymonthday_negative) if ( ($this->bymonthday || $this->bymonthday_negative)
&& ! in_array($masks['yearday_to_monthday'][$yearday], $this->bymonthday) && ! in_array($this->_masks['yearday_to_monthday'][$this->_yearday], $this->bymonthday)
&& ! in_array($masks['yearday_to_monthday_negative'][$yearday], $this->bymonthday_negative) ) { && ! in_array($this->_masks['yearday_to_monthday_negative'][$this->_yearday], $this->bymonthday_negative) ) {
continue; continue;
} }
if ( $this->byweekday && ! in_array($masks['yearday_to_weekday'][$yearday], $this->byweekday) ) { if ( $this->byweekday && ! in_array($this->_masks['yearday_to_weekday'][$this->_yearday], $this->byweekday) ) {
continue; continue;
} }
if ( $this->byweekday_nth && ! isset($masks['yearday_is_nth_weekday'][$yearday]) ) { if ( $this->byweekday_nth && ! isset($this->_masks['yearday_is_nth_weekday'][$this->_yearday]) ) {
continue; continue;
} }
$filtered_set[] = $yearday; $filtered_set[] = $this->_yearday;
} }
$dayset = $filtered_set; $this->_dayset = $filtered_set;
// if BYSETPOS is set, we need to expand the timeset to filter by pos // if BYSETPOS is set, we need to expand the timeset to filter by pos
// so we make a special loop to return while generating // so we make a special loop to return while generating
if ( $this->bysetpos && $timeset ) { if ( $this->bysetpos && $this->_timeset ) {
$filtered_set = array(); $filtered_set = array();
foreach ( $this->bysetpos as $pos ) { foreach ( $this->bysetpos as $pos ) {
$n = count($timeset); $n = count($this->_timeset);
if ( $pos < 0 ) { if ( $pos < 0 ) {
$pos = $n * count($dayset) + $pos; $pos = $n * count($this->_dayset) + $pos;
} }
else { else {
$pos = $pos - 1; $pos = $pos - 1;
@ -1522,15 +1547,15 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
$div = (int) ($pos / $n); // daypos $div = (int) ($pos / $n); // daypos
$mod = $pos % $n; // timepos $mod = $pos % $n; // timepos
if ( isset($dayset[$div]) && isset($timeset[$mod]) ) { if ( isset($this->_dayset[$div]) && isset($this->_timeset[$mod]) ) {
$yearday = $dayset[$div]; $this->_yearday = $this->_dayset[$div];
$time = $timeset[$mod]; $time = $this->_timeset[$mod];
// used as array key to ensure uniqueness // used as array key to ensure uniqueness
$tmp = $year.':'.$yearday.':'.$time[0].':'.$time[1].':'.$time[2]; $tmp = $this->_year.':'.$this->_yearday.':'.$time[0].':'.$time[1].':'.$time[2];
if ( ! isset($filtered_set[$tmp]) ) { if ( ! isset($filtered_set[$tmp]) ) {
$occurrence = \DateTime::createFromFormat( $occurrence = \DateTime::createFromFormat(
'Y z', 'Y z',
"$year $yearday", "$this->_year $this->_yearday",
$this->dtstart->getTimezone() $this->dtstart->getTimezone()
); );
$occurrence->setTime($time[0], $time[1], $time[2]); $occurrence->setTime($time[0], $time[1], $time[2]);
@ -1539,25 +1564,25 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
} }
} }
sort($filtered_set); sort($filtered_set);
$dayset = $filtered_set; $this->_dayset = $filtered_set;
} }
} }
// 2. loop, generate a valid date, and return the result (fake "yield") // 2. loop, generate a valid date, and return the result (fake "yield")
// at the same time, we check the end condition and return null if // at the same time, we check the end condition and return null if
// we need to stop // we need to stop
if ( $this->bysetpos && $timeset ) { if ( $this->bysetpos && $this->_timeset ) {
while ( ($occurrence = current($dayset)) !== false ) { while ( ($occurrence = current($this->_dayset)) !== false ) {
// consider end conditions // consider end conditions
if ( $this->until && $occurrence > $this->until ) { if ( $this->until && $occurrence > $this->until ) {
$this->total = $total; // save total for count() cache $this->total = $this->_total; // save total for count() cache
return null; return null;
} }
next($dayset); next($this->_dayset);
if ( $occurrence >= $dtstart ) { // ignore occurrences before DTSTART if ( $occurrence >= $this->_dtstart ) { // ignore occurrences before DTSTART
$total += 1; $this->_total += 1;
$this->cache[] = $occurrence; $this->cache[] = $occurrence;
return $occurrence; // yield return $occurrence; // yield
} }
@ -1565,57 +1590,57 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
} }
else { else {
// normal loop, without BYSETPOS // normal loop, without BYSETPOS
while ( ($yearday = current($dayset)) !== false ) { while ( ($this->_yearday = current($this->_dayset)) !== false ) {
$occurrence = \DateTime::createFromFormat('Y z', "$year $yearday",$this->dtstart->getTimezone()); $occurrence = \DateTime::createFromFormat('Y z', "$this->_year $this->_yearday",$this->dtstart->getTimezone());
while ( ($time = current($timeset)) !== false ) { while ( ($time = current($this->_timeset)) !== false ) {
$occurrence->setTime($time[0], $time[1], $time[2]); $occurrence->setTime($time[0], $time[1], $time[2]);
// consider end conditions // consider end conditions
if ( $this->until && $occurrence > $this->until ) { if ( $this->until && $occurrence > $this->until ) {
$this->total = $total; // save total for count() cache $this->total = $this->_total; // save total for count() cache
return null; return null;
} }
next($timeset); next($this->_timeset);
if ( $occurrence >= $dtstart ) { // ignore occurrences before DTSTART if ( $occurrence >= $this->_dtstart ) { // ignore occurrences before DTSTART
$total += 1; $this->_total += 1;
$this->cache[] = $occurrence; $this->cache[] = $occurrence;
return $occurrence; // yield return $occurrence; // yield
} }
} }
reset($timeset); reset($this->_timeset);
next($dayset); next($this->_dayset);
} }
} }
// 3. we reset the loop to the next interval // 3. we reset the loop to the next interval
$days_increment = 0; $this->_days_increment = 0;
switch ( $this->freq ) { switch ( $this->freq ) {
case self::YEARLY: case self::YEARLY:
// we do not care about $month or $day not existing, // we do not care about $this->_month or $this->_day not existing,
// they are not used in yearly frequency // they are not used in yearly frequency
$year = $year + $this->interval; $this->_year = $this->_year + $this->interval;
break; break;
case self::MONTHLY: case self::MONTHLY:
// we do not care about the day of the month not existing // we do not care about the day of the month not existing
// it is not used in monthly frequency // it is not used in monthly frequency
$month = $month + $this->interval; $this->_month = $this->_month + $this->interval;
if ( $month > 12 ) { if ( $this->_month > 12 ) {
$div = (int) ($month / 12); $div = (int) ($this->_month / 12);
$mod = $month % 12; $mod = $this->_month % 12;
$month = $mod; $this->_month = $mod;
$year = $year + $div; $this->_year = $this->_year + $div;
if ( $month == 0 ) { if ( $this->_month == 0 ) {
$month = 12; $this->_month = 12;
$year = $year - 1; $this->_year = $this->_year - 1;
} }
} }
break; break;
case self::WEEKLY: case self::WEEKLY:
$days_increment = $this->interval*7; $this->_days_increment = $this->interval*7;
break; break;
case self::DAILY: case self::DAILY:
$days_increment = $this->interval; $this->_days_increment = $this->interval;
break; break;
// For the time frequencies, things are a little bit different. // For the time frequencies, things are a little bit different.
@ -1626,124 +1651,125 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
// call the DateTime method at the very end. // call the DateTime method at the very end.
case self::HOURLY: case self::HOURLY:
if ( empty($dayset) ) { if ( empty($this->_dayset) ) {
// an empty set means that this day has been filtered out // an empty set means that this day has been filtered out
// by one of the BYXXX rule. So there is no need to // by one of the BYXXX rule. So there is no need to
// examine it any further, we know nothing is going to // examine it any further, we know nothing is going to
// occur anyway. // occur anyway.
// so we jump to one iteration right before next day // so we jump to one iteration right before next day
$hour += ((int) ((23 - $hour) / $this->interval)) * $this->interval; $this->_hour += ((int) ((23 - $this->_hour) / $this->interval)) * $this->interval;
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::HOURLY]; $j++ ) { for ( $j = 0; $j < self::$REPEAT_CYCLES[self::HOURLY]; $j++ ) {
$hour += $this->interval; $this->_hour += $this->interval;
$div = (int) ($hour / 24); $div = (int) ($this->_hour / 24);
$mod = $hour % 24; $mod = $this->_hour % 24;
if ( $div ) { if ( $div ) {
$hour = $mod; $this->_hour = $mod;
$days_increment += $div; $this->_days_increment += $div;
} }
if ( ! $this->byhour || in_array($hour, $this->byhour)) { if ( ! $this->byhour || in_array($this->_hour, $this->byhour)) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if ( ! $found ) {
$this->total = $total; // save total for count cache $this->total = $this->_total; // save total for count cache
return null; // stop the iterator return null; // stop the iterator
} }
$timeset = $this->getTimeSet($hour, $minute, $second); $this->_timeset = $this->getTimeSet($this->_hour, $this->_minute, $this->_second);
break; break;
case self::MINUTELY: case self::MINUTELY:
if ( empty($dayset) ) { if ( empty($this->_dayset) ) {
$minute += ((int) ((1439 - ($hour*60+$minute)) / $this->interval)) * $this->interval; $this->_minute += ((int) ((1439 - ($this->_hour*60+$this->_minute)) / $this->interval)) * $this->interval;
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::MINUTELY]; $j++ ) { for ( $j = 0; $j < self::$REPEAT_CYCLES[self::MINUTELY]; $j++ ) {
$minute += $this->interval; $this->_minute += $this->interval;
$div = (int) ($minute / 60); $div = (int) ($this->_minute / 60);
$mod = $minute % 60; $mod = $this->_minute % 60;
if ( $div ) { if ( $div ) {
$minute = $mod; $this->_minute = $mod;
$hour += $div; $this->_hour += $div;
$div = (int) ($hour / 24); $div = (int) ($this->_hour / 24);
$mod = $hour % 24; $mod = $this->_hour % 24;
if ( $div ) { if ( $div ) {
$hour = $mod; $this->_hour = $mod;
$days_increment += $div; $this->_days_increment += $div;
} }
} }
if ( (! $this->byhour || in_array($hour, $this->byhour)) && if ( (! $this->byhour || in_array($this->_hour, $this->byhour)) &&
(! $this->byminute || in_array($minute, $this->byminute)) ) { (! $this->byminute || in_array($this->_minute, $this->byminute)) ) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if ( ! $found ) {
$this->total = $total; // save total for count cache $this->total = $this->_total; // save total for count cache
return null; // stop the iterator return null; // stop the iterator
} }
$timeset = $this->getTimeSet($hour, $minute, $second); $this->_timeset = $this->getTimeSet($this->_hour, $this->_minute, $this->_second);
break; break;
case self::SECONDLY: case self::SECONDLY:
if ( empty($dayset) ) { if ( empty($this->_dayset) ) {
$second += ((int) ((86399 - ($hour*3600 + $minute*60 + $second)) / $this->interval)) * $this->interval; $this->_second += ((int) ((86399 - ($this->_hour*3600 + $this->_minute*60 + $this->_second)) / $this->interval)) * $this->interval;
} }
$found = false; $found = false;
for ( $j = 0; $j < self::$REPEAT_CYCLES[self::SECONDLY]; $j++ ) { for ( $j = 0; $j < self::$REPEAT_CYCLES[self::SECONDLY]; $j++ ) {
$second += $this->interval; $this->_second += $this->interval;
$div = (int) ($second / 60); $div = (int) ($this->_second / 60);
$mod = $second % 60; $mod = $this->_second % 60;
if ( $div ) { if ( $div ) {
$second = $mod; $this->_second = $mod;
$minute += $div; $this->_minute += $div;
$div = (int) ($minute / 60); $div = (int) ($this->_minute / 60);
$mod = $minute % 60; $mod = $this->_minute % 60;
if ( $div ) { if ( $div ) {
$minute = $mod; $this->_minute = $mod;
$hour += $div; $this->_hour += $div;
$div = (int) ($hour / 24); $div = (int) ($this->_hour / 24);
$mod = $hour % 24; $mod = $this->_hour % 24;
if ( $div ) { if ( $div ) {
$hour = $mod; $this->_hour = $mod;
$days_increment += $div; $this->_days_increment += $div;
} }
} }
} }
if ( ( ! $this->byhour || in_array($hour, $this->byhour) ) if ( ( ! $this->byhour || in_array($this->_hour, $this->byhour) )
&& ( ! $this->byminute || in_array($minute, $this->byminute) ) && ( ! $this->byminute || in_array($this->_minute, $this->byminute) )
&& ( ! $this->bysecond || in_array($second, $this->bysecond) ) ) { && ( ! $this->bysecond || in_array($this->_second, $this->bysecond) ) ) {
$found = true; $found = true;
break; break;
} }
} }
if ( ! $found ) { if ( ! $found ) {
$this->total = $total; // save total for count cache $this->total = $this->_total; // save total for count cache
return null; // stop the iterator return null; // stop the iterator
} }
$timeset = $this->getTimeSet($hour, $minute, $second); $this->_timeset = $this->getTimeSet($this->_hour, $this->_minute, $this->_second);
break; break;
} }
// here we take a little shortcut from the Python version, by using DateTime // here we take a little shortcut from the Python version, by using DateTime
if ( $days_increment ) { if ( $this->_days_increment ) {
list($year,$month,$day) = explode('-',date_create("$year-$month-$day")->modify("+ $days_increment days")->format('Y-n-j')); list($this->_year,$this->_month,$this->_day) = explode('-',date_create("$this->_year-$this->_month-$this->_day")->modify("+ $this->_days_increment days")->format('Y-n-j'));
} }
$dayset = null; // reset the loop $this->_dayset = null; // reset the loop
} }
$this->total = $total; // save total for count cache $this->total = $this->_total; // save total for count cache
return null; // stop the iterator return null; // stop the iterator
} }
///////////////////////////////////////////////////////////////////////////////
// constants // constants
// Every mask is 7 days longer to handle cross-year weekly periods. // Every mask is 7 days longer to handle cross-year weekly periods.
@ -1893,6 +1919,7 @@ class RRule implements \Iterator, \ArrayAccess, \Countable
self::SECONDLY => 86400 // that's a lot of cycles too self::SECONDLY => 86400 // that's a lot of cycles too
); );
///////////////////////////////////////////////////////////////////////////////
// i18n methods (could be moved into a separate class, since it's not always necessary) // i18n methods (could be moved into a separate class, since it's not always necessary)
/** /**