* or the GNU Affero General Public License, version 3: * iCalcreator 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. * iCalcreator is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public * License along with this program. * If not, see . */ namespace kigkonsult\iCalcreator\util; /** * iCalcreator recur support class * * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.23.18 - 2017-06-14 */ class utilRecur { /** * Static values for recurrence FREQuence * @access private * @static */ private static $DAILY = 'DAILY'; private static $WEEKLY = 'WEEKLY'; private static $MONTHLY = 'MONTHLY'; private static $YEARLY = 'YEARLY'; //private static $SECONDLY = 'SECONDLY'; //private static $MINUTELY = 'MINUTELY'; //private static $HOURLY = 'HOURLY'; private static $DAYNAMES = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; private static $YEARCNT_UP = 'yearcnt_up'; private static $YEARCNT_DOWN = 'yearcnt_down'; private static $MONTHDAYNO_UP = 'monthdayno_up'; private static $MONTHDAYNO_DOWN = 'monthdayno_down'; private static $MONTHCNT_DOWN = 'monthcnt_down'; private static $YEARDAYNO_UP = 'yeardayno_up'; private static $YEARDAYNO_DOWN = 'yeardayno_down'; private static $WEEKNO_UP = 'weekno_up'; private static $WEEKNO_DOWN = 'weekno_down'; private static $W = 'W'; /** * Sort recur dates * @param array $byDayA * @param array $byDayB * @return int * @access private * @static */ private static function recurBydaySort( $byDayA, $byDayB ) { static $days = ['SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6]; return ( $days[substr( $byDayA, -2 )] < $days[substr( $byDayB, -2 )] ) ? -1 : 1; } /** * Return formatted output for calendar component property data value type recur * * @param string $recurlabel * @param array $recurData * @param bool $allowEmpty * @return string * @static */ public static function formatRecur( $recurlabel, $recurData, $allowEmpty ) { static $FMTFREQEQ = 'FREQ=%s'; static $FMTDEFAULTEQ = ';%s=%s'; static $FMTOTHEREQ = ';%s='; static $RECURBYDAYSORTER = null; static $SP0 = ''; if( is_null( $RECURBYDAYSORTER )) $RECURBYDAYSORTER = [get_class(), 'recurBydaySort']; if( empty( $recurData )) return null; $output = null; foreach( $recurData as $rx => $theRule ) { if( empty( $theRule[util::$LCvalue] )) { if( $allowEmpty ) $output .= util::createElement( $recurlabel ); continue; } $attributes = ( isset( $theRule[util::$LCparams] )) ? util::createParams( $theRule[util::$LCparams] ) : null; $content1 = $content2 = null; foreach( $theRule[util::$LCvalue] as $ruleLabel => $ruleValue ) { $ruleLabel = strtoupper( $ruleLabel ); switch( $ruleLabel ) { case util::$FREQ : $content1 .= sprintf( $FMTFREQEQ, $ruleValue ); break; case util::$UNTIL : $parno = ( isset( $ruleValue[util::$LCHOUR] )) ? 7 : 3; $content2 .= sprintf( $FMTDEFAULTEQ, util::$UNTIL, util::date2strdate( $ruleValue, $parno )); break; case util::$COUNT : case util::$INTERVAL : case util::$WKST : $content2 .= sprintf( $FMTDEFAULTEQ, $ruleLabel, $ruleValue ); break; case util::$BYDAY : $byday = [$SP0]; $bx = 0; foreach( $ruleValue as $bix => $bydayPart ) { if( ! empty( $byday[$bx] ) && // new day ! ctype_digit( substr( $byday[$bx], -1 ))) $byday[++$bx] = $SP0; if( ! is_array( $bydayPart )) // day without order number $byday[$bx] .= (string) $bydayPart; else { // day with order number foreach( $bydayPart as $bix2 => $bydayPart2 ) $byday[$bx] .= (string) $bydayPart2; } } // end foreach( $ruleValue as $bix => $bydayPart ) if( 1 < count( $byday )) usort( $byday, $RECURBYDAYSORTER ); $content2 .= sprintf( $FMTDEFAULTEQ, util::$BYDAY, implode( util::$COMMA, $byday )); break; default : // BYSECOND/BYMINUTE/BYHOUR/BYMONTHDAY/BYYEARDAY/BYWEEKNO/BYMONTH/BYSETPOS... if( is_array( $ruleValue )) { $content2 .= sprintf( $FMTOTHEREQ, $ruleLabel ); $content2 .= implode( util::$COMMA, $ruleValue ); } else $content2 .= sprintf( $FMTDEFAULTEQ, $ruleLabel, $ruleValue ); break; } // end switch( $ruleLabel ) } // end foreach( $theRule[util::$LCvalue] )) as $ruleLabel => $ruleValue ) $output .= util::createElement( $recurlabel, $attributes, $content1 . $content2 ); } // end foreach( $recurData as $rx => $theRule ) return $output; } /** * Convert input format for EXRULE and RRULE to internal format * * @param array $rexrule * @return array * @static */ public static function setRexrule( $rexrule ) { static $BYSECOND = 'BYSECOND'; static $BYMINUTE = 'BYMINUTE'; static $BYHOUR = 'BYHOUR'; $input = []; if( empty( $rexrule )) return $input; $rexrule = array_change_key_case( $rexrule, CASE_UPPER ); foreach( $rexrule as $rexruleLabel => $rexruleValue ) { if( util::$UNTIL != $rexruleLabel ) $input[$rexruleLabel] = $rexruleValue; else { util::strDate2arr( $rexruleValue ); if( util::isArrayTimestampDate( $rexruleValue )) // timestamp, always date-time UTC $input[$rexruleLabel] = util::timestamp2date( $rexruleValue, 7, util::$UTC ); elseif( util::isArrayDate( $rexruleValue )) { // date or UTC date-time $parno = ( isset( $rexruleValue[util::$LCHOUR] ) || isset( $rexruleValue[4] )) ? 7 : 3; $d = util::chkDateArr( $rexruleValue, $parno ); if(( 3 < $parno ) && isset( $d[util::$LCtz] ) && ( util::$Z != $d[util::$LCtz] ) && util::isOffset( $d[util::$LCtz] )) { $input[$rexruleLabel] = util::strDate2ArrayDate( sprintf( util::$YMDHISE, (int) $d[util::$LCYEAR], (int) $d[util::$LCMONTH], (int) $d[util::$LCDAY], (int) $d[util::$LCHOUR], (int) $d[util::$LCMIN], (int) $d[util::$LCSEC], $d[util::$LCtz] ), 7 ); unset( $input[$rexruleLabel][util::$UNPARSEDTEXT] ); } else $input[$rexruleLabel] = $d; } elseif( 8 <= strlen( trim( $rexruleValue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC $input[$rexruleLabel] = util::strDate2ArrayDate( $rexruleValue ); unset( $input[$rexruleLabel][util::$UNPARSEDTEXT] ); } if(( 3 < count( $input[$rexruleLabel] )) && ! isset( $input[$rexruleLabel][util::$LCtz] )) $input[$rexruleLabel][util::$LCtz] = util::$Z; } } // end foreach( $rexrule as $rexruleLabel => $rexruleValue ) /* set recurrence rule specification in rfc2445 order */ $input2 = []; if( isset( $input[util::$FREQ] )) $input2[util::$FREQ] = $input[util::$FREQ]; if( isset( $input[util::$UNTIL] )) $input2[util::$UNTIL] = $input[util::$UNTIL]; elseif( isset( $input[util::$COUNT] )) $input2[util::$COUNT] = $input[util::$COUNT]; if( isset( $input[util::$INTERVAL] )) $input2[util::$INTERVAL] = $input[util::$INTERVAL]; if( isset( $input[$BYSECOND] )) $input2[$BYSECOND] = $input[$BYSECOND]; if( isset( $input[$BYMINUTE] )) $input2[$BYMINUTE] = $input[$BYMINUTE]; if( isset( $input[$BYHOUR] )) $input2[$BYHOUR] = $input[$BYHOUR]; if( isset( $input[util::$BYDAY] )) { if( ! is_array( $input[util::$BYDAY] )) // ensure upper case.. . $input2[util::$BYDAY] = strtoupper( $input[util::$BYDAY] ); else { foreach( $input[util::$BYDAY] as $BYDAYx => $BYDAYv ) { if( 0 == strcasecmp( util::$DAY, $BYDAYx )) $input2[util::$BYDAY][util::$DAY] = strtoupper( $BYDAYv ); elseif( ! is_array( $BYDAYv )) $input2[util::$BYDAY][$BYDAYx] = $BYDAYv; else { foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) { if( 0 == strcasecmp( util::$DAY, $BYDAYx2 )) $input2[util::$BYDAY][$BYDAYx][util::$DAY] = strtoupper( $BYDAYv2 ); else $input2[util::$BYDAY][$BYDAYx][$BYDAYx2] = $BYDAYv2; } } } } } // end if( isset( $input[util::$BYDAY] )) if( isset( $input[util::$BYMONTHDAY] )) $input2[util::$BYMONTHDAY] = $input[util::$BYMONTHDAY]; if( isset( $input[util::$BYYEARDAY] )) $input2[util::$BYYEARDAY] = $input[util::$BYYEARDAY]; if( isset( $input[util::$BYWEEKNO] )) $input2[util::$BYWEEKNO] = $input[util::$BYWEEKNO]; if( isset( $input[util::$BYMONTH] )) $input2[util::$BYMONTH] = $input[util::$BYMONTH]; if( isset( $input[util::$BYSETPOS] )) $input2[util::$BYSETPOS] = $input[util::$BYSETPOS]; if( isset( $input[util::$WKST] )) $input2[util::$WKST] = $input[util::$WKST]; return $input2; } /** * Update array $result with dates based on a recur pattern * * If missing, UNTIL is set 1 year from startdate (emergency break) * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.21.11 - 2015-03-10 * @param array $result array to update, array([Y-m-d] => bool) * @param array $recur pattern for recurrency (only value part, params ignored) * @param mixed $wdate component start date, string / array / (datetime) obj * @param mixed $fcnStart start date, string / array / (datetime) obj * @param mixed $fcnEnd end date, string / array / (datetime) obj * @static * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start OR not at all */ public static function recur2date( & $result, $recur, $wdate, $fcnStart, $fcnEnd=false ) { static $YEAR2DAYARR = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY']; static $SU = 'SU'; self::reFormatDate( $wdate ); $wdateYMD = sprintf( util::$YMD, $wdate[util::$LCYEAR], $wdate[util::$LCMONTH], $wdate[util::$LCDAY] ); $wdateHis = sprintf( util::$HIS, $wdate[util::$LCHOUR], $wdate[util::$LCMIN], $wdate[util::$LCSEC] ); $untilHis = $wdateHis; self::reFormatDate( $fcnStart ); $fcnStartYMD = sprintf( util::$YMD, $fcnStart[util::$LCYEAR], $fcnStart[util::$LCMONTH], $fcnStart[util::$LCDAY] ); if( ! empty( $fcnEnd )) self::reFormatDate( $fcnEnd ); else { $fcnEnd = $fcnStart; $fcnEnd[util::$LCYEAR] += 1; } $fcnEndYMD = sprintf( util::$YMD, $fcnEnd[util::$LCYEAR], $fcnEnd[util::$LCMONTH], $fcnEnd[util::$LCDAY] ); // echo "recur _in_ comp start ".implode('-',$wdate)." period start ".implode('-',$fcnStart)." period end ".implode('-',$fcnEnd)."
\n"; // echo 'recur='.str_replace( [PHP_EOL, ' '], null, var_export( $recur, true ))."
\n"; // test ### if( ! isset( $recur[util::$COUNT] ) && ! isset( $recur[util::$UNTIL] )) $recur[util::$UNTIL] = $fcnEnd; // create break if( isset( $recur[util::$UNTIL] )) { foreach( $recur[util::$UNTIL] as $k => $v ) { if( ctype_digit( $v )) $recur[util::$UNTIL][$k] = (int) $v; } unset( $recur[util::$UNTIL][util::$LCtz] ); if( $fcnEnd > $recur[util::$UNTIL] ) { $fcnEnd = $recur[util::$UNTIL]; // emergency break $fcnEndYMD = sprintf( util::$YMD, $fcnEnd[util::$LCYEAR], $fcnEnd[util::$LCMONTH], $fcnEnd[util::$LCDAY] ); } if( isset( $recur[util::$UNTIL][util::$LCHOUR] )) $untilHis = sprintf( util::$HIS, $recur[util::$UNTIL][util::$LCHOUR], $recur[util::$UNTIL][util::$LCMIN], $recur[util::$UNTIL][util::$LCSEC] ); else $untilHis = sprintf( util::$HIS, 23, 59, 59 ); // echo 'recurUNTIL='.str_replace( [PHP_EOL, ' '], '', var_export( $recur['UNTIL'], true )).", untilHis={$untilHis}
\n"; // test ### } // end if( isset( $recur[util::$UNTIL] )) // echo 'fcnEnd:'.$fcnEndYMD.$untilHis."
\n"; // test ### if( $wdateYMD > $fcnEndYMD ) { // echo 'recur out of date, '.implode('-',$wdate).', end='.implode('-',$fcnEnd)."
\n"; // test ### return []; // nothing to do.. . } if( ! isset( $recur[util::$FREQ] )) // "MUST be specified.. ." $recur[util::$FREQ] = self::$DAILY; // ?? $wkst = ( isset( $recur[util::$WKST] ) && ( $SU == $recur[util::$WKST] )) ? 24*60*60 : 0; // ?? if( ! isset( $recur[util::$INTERVAL] )) $recur[util::$INTERVAL] = 1; $recurCount = ( ! isset( $recur[util::$BYSETPOS] )) ? 1 : 0; // DTSTART counts as the first occurrence /* find out how to step up dates and set index for interval count */ $step = []; if( self::$YEARLY == $recur[util::$FREQ] ) $step[util::$LCYEAR] = 1; elseif( self::$MONTHLY == $recur[util::$FREQ] ) $step[util::$LCMONTH] = 1; elseif( self::$WEEKLY == $recur[util::$FREQ] ) $step[util::$LCDAY] = 7; else $step[util::$LCDAY] = 1; if( isset( $step[util::$LCYEAR] ) && isset( $recur[util::$BYMONTH] )) $step = [util::$LCMONTH => 1]; if( empty( $step ) && isset( $recur[util::$BYWEEKNO] )) // ?? $step = [util::$LCDAY => 7]; if( isset( $recur[util::$BYYEARDAY] ) || isset( $recur[util::$BYMONTHDAY] ) || isset( $recur[util::$BYDAY] )) $step = [util::$LCDAY => 1]; $intervalarr = []; if( 1 < $recur[util::$INTERVAL] ) { $intervalix = self::recurIntervalIx( $recur[util::$FREQ], $wdate, $wkst ); $intervalarr = [$intervalix => 0]; } if( isset( $recur[util::$BYSETPOS] )) { // save start date + weekno $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = []; if( is_array( $recur[util::$BYSETPOS] )) { foreach( $recur[util::$BYSETPOS] as $bix => $bval ) $recur[util::$BYSETPOS][$bix] = (int) $bval; } else $recur[util::$BYSETPOS] = [(int) $recur[util::$BYSETPOS]]; if( self::$YEARLY == $recur[util::$FREQ] ) { $wdate[util::$LCMONTH] = $wdate[util::$LCDAY] = 1; // start from beginning of year $wdateYMD = sprintf( util::$YMD, $wdate[util::$LCYEAR], $wdate[util::$LCMONTH], $wdate[util::$LCDAY] ); self::stepdate( $fcnEnd, $fcnEndYMD, [util::$LCYEAR => 1] ); // make sure to count last year } elseif( self::$MONTHLY == $recur[util::$FREQ] ) { $wdate[util::$LCDAY] = 1; // start from beginning of month $wdateYMD = sprintf( util::$YMD, $wdate[util::$LCYEAR], $wdate[util::$LCMONTH], $wdate[util::$LCDAY] ); self::stepdate( $fcnEnd, $fcnEndYMD, [util::$LCMONTH => 1] ); // make sure to count last month } else self::stepdate( $fcnEnd, $fcnEndYMD, $step); // make sure to count whole last period // echo "BYSETPOS endDat =".implode('-',$fcnEnd).' step='.var_export($step,true)."
\n"; // test ###### $bysetposWold = (int) date( self::$W, mktime( 0, 0, $wkst, $wdate[util::$LCMONTH], $wdate[util::$LCDAY], $wdate[util::$LCYEAR] )); $bysetposYold = $wdate[util::$LCYEAR]; $bysetposMold = $wdate[util::$LCMONTH]; $bysetposDold = $wdate[util::$LCDAY]; } // end if( isset( $recur[util::$BYSETPOS] )) else self::stepdate( $wdate, $wdateYMD, $step); $year_old = null; /* MAIN LOOP */ while( true ) { // $txt = ( isset( $recur[util::$COUNT] )) ? '. recurCount : ' . $recurCount . ', COUNT : ' . $recur[util::$COUNT] : null; // test ### // echo "recur start while:{$wdateYMD}, end:{$fcnEndYMD}{$txt}
\n"; // test ### if( $wdateYMD.$wdateHis > $fcnEndYMD.$untilHis ) break; if( isset( $recur[util::$COUNT] ) && ( $recurCount >= $recur[util::$COUNT] )) break; if( $year_old != $wdate[util::$LCYEAR] ) { // $year_old=null 1:st time $year_old = $wdate[util::$LCYEAR]; $daycnts = self::initDayCnts( $wdate, $recur, $wkst ); } /* check interval */ if( 1 < $recur[util::$INTERVAL] ) { /* create interval index */ $intervalix = self::recurIntervalIx( $recur[util::$FREQ], $wdate, $wkst ); /* check interval */ $currentKey = array_keys( $intervalarr ); $currentKey = end( $currentKey ); // get last index if( $currentKey != $intervalix ) $intervalarr = [$intervalix => ( $intervalarr[$currentKey] + 1 )]; if(( $recur[util::$INTERVAL] != $intervalarr[$intervalix] ) && ( 0 != $intervalarr[$intervalix] )) { /* step up date */ // echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
\n"; // test ### self::stepdate( $wdate, $wdateYMD, $step); continue; } else // continue within the selected interval $intervalarr[$intervalix] = 0; // echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
\n"; // test ### } // endif( 1 < $recur['INTERVAL'] ) $updateOK = true; if( $updateOK && isset( $recur[util::$BYMONTH] )) $updateOK = self::recurBYcntcheck( $recur[util::$BYMONTH], $wdate[util::$LCMONTH], ( $wdate[util::$LCMONTH] - 13 )); if( $updateOK && isset( $recur[util::$BYWEEKNO] )) $updateOK = self::recurBYcntcheck( $recur[util::$BYWEEKNO], $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$WEEKNO_UP], $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$WEEKNO_DOWN] ); if( $updateOK && isset( $recur[util::$BYYEARDAY] )) $updateOK = self::recurBYcntcheck( $recur[util::$BYYEARDAY], $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$YEARCNT_UP], $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$YEARCNT_DOWN] ); if( $updateOK && isset( $recur[util::$BYMONTHDAY] )) $updateOK = self::recurBYcntcheck( $recur[util::$BYMONTHDAY], $wdate[util::$LCDAY], $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$MONTHCNT_DOWN] ); // echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'true' : 'false'; echo "
\n"; // test ###### if( $updateOK && isset( $recur[util::$BYDAY] )) { $updateOK = false; $m = $wdate[util::$LCMONTH]; $d = $wdate[util::$LCDAY]; if( isset( $recur[util::$BYDAY][util::$DAY] )) { // single day, opt with year/month day order no $daynoexists = $daynosw = $daynamesw = false; if( $recur[util::$BYDAY][util::$DAY] == $daycnts[$m][$d][util::$DAY] ) $daynamesw = true; if( isset( $recur[util::$BYDAY][0] )) { $daynoexists = true; if(( isset( $recur[util::$FREQ] ) && ( $recur[util::$FREQ] == self::$MONTHLY )) || isset( $recur[util::$BYMONTH] )) $daynosw = self::recurBYcntcheck( $recur[util::$BYDAY][0], $daycnts[$m][$d][self::$MONTHDAYNO_UP], $daycnts[$m][$d][self::$MONTHDAYNO_DOWN] ); elseif( isset( $recur[util::$FREQ] ) && ( $recur[util::$FREQ] == self::$YEARLY )) $daynosw = self::recurBYcntcheck( $recur[util::$BYDAY][0], $daycnts[$m][$d][self::$YEARDAYNO_UP], $daycnts[$m][$d][self::$YEARDAYNO_DOWN] ); } if(( $daynoexists && $daynosw && $daynamesw ) || ( ! $daynoexists && ! $daynosw && $daynamesw )) { $updateOK = true; // echo "m=$m d=$d day=".$daycnts[$m][$d][util::$DAY]." yeardayno_up=".$daycnts[$m][$d][self::$YEARDAYNO_UP]." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK
\n"; // test ### } // echo "m=$m d=$d day=".$daycnts[$m][$d][util::$DAY]." yeardayno_up=".$daycnts[$m][$d][self::$YEARDAYNO_UP]." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK
\n"; // test ### } else { foreach( $recur[util::$BYDAY] as $bydayvalue ) { $daynoexists = $daynosw = $daynamesw = false; if( isset( $bydayvalue[util::$DAY] ) && ( $bydayvalue[util::$DAY] == $daycnts[$m][$d][util::$DAY] )) $daynamesw = true; if( isset( $bydayvalue[0] )) { $daynoexists = true; if(( isset( $recur[util::$FREQ] ) && ( $recur[util::$FREQ] == self::$MONTHLY )) || isset( $recur[util::$BYMONTH] )) $daynosw = self::recurBYcntcheck( $bydayvalue['0'], $daycnts[$m][$d][self::$MONTHDAYNO_UP], $daycnts[$m][$d][self::$MONTHDAYNO_DOWN] ); elseif( isset( $recur[util::$FREQ] ) && ( $recur[util::$FREQ] == self::$YEARLY )) $daynosw = self::recurBYcntcheck( $bydayvalue['0'], $daycnts[$m][$d][self::$YEARDAYNO_UP], $daycnts[$m][$d][self::$YEARDAYNO_DOWN] ); } // echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
\n"; // test ### if(( $daynoexists && $daynosw && $daynamesw ) || ( ! $daynoexists && ! $daynosw && $daynamesw )) { $updateOK = true; break; } } } } // echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'true' : 'false'; echo "
\n"; // test ### /* check BYSETPOS */ if( $updateOK ) { if( isset( $recur[util::$BYSETPOS] ) && ( in_array( $recur[util::$FREQ], $YEAR2DAYARR))) { if( isset( $recur[self::$WEEKLY] )) { if( $bysetposWold == $daycnts[$wdate[util::$LCMONTH]][$wdate[util::$LCDAY]][self::$WEEKNO_UP] ) $bysetposw1[] = $wdateYMD; else $bysetposw2[] = $wdateYMD; } else { if(( isset( $recur[util::$FREQ] ) && ( self::$YEARLY == $recur[util::$FREQ] ) && ( $bysetposYold == $wdate[util::$LCYEAR] )) || ( isset( $recur[util::$FREQ] ) && ( self::$MONTHLY == $recur[util::$FREQ] ) && (( $bysetposYold == $wdate[util::$LCYEAR] ) && ( $bysetposMold == $wdate[util::$LCMONTH] ))) || ( isset( $recur[util::$FREQ] ) && ( self::$DAILY == $recur[util::$FREQ] ) && (( $bysetposYold == $wdate[util::$LCYEAR] ) && ( $bysetposMold == $wdate[util::$LCMONTH]) && ( $bysetposDold == $wdate[util::$LCDAY] )))) { // echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."
\n"; // test ### $bysetposymd1[] = $wdateYMD; } else { // echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."
\n"; // test ### $bysetposymd2[] = $wdateYMD; } } // end else } else { if( checkdate((int) $wdate[util::$LCMONTH], (int) $wdate[util::$LCDAY], (int) $wdate[util::$LCYEAR] )) { /* update result array if BYSETPOS is not set */ $recurCount++; if( $fcnStartYMD <= $wdateYMD ) { // only output within period $result[$wdateYMD] = true; // echo "recur $wdateYMD, recurCount:{$recurCount}
\n"; // test ### } } // else echo "recur, no date $wdateYMD
\n"; // test ### $updateOK = false; } } /* step up date */ self::stepdate( $wdate, $wdateYMD, $step); /* check if BYSETPOS is set for updating result array */ if( $updateOK && isset( $recur[util::$BYSETPOS] )) { $bysetpos = false; if( isset( $recur[util::$FREQ] ) && ( self::$YEARLY == $recur[util::$FREQ] ) && ( $bysetposYold != $wdate[util::$LCYEAR] )) { $bysetpos = true; $bysetposYold = $wdate[util::$LCYEAR]; } elseif( isset( $recur[util::$FREQ] ) && ( self::$MONTHLY == $recur[util::$FREQ] && (( $bysetposYold != $wdate[util::$LCYEAR] ) || ( $bysetposMold != $wdate[util::$LCMONTH] )))) { $bysetpos = true; $bysetposYold = $wdate[util::$LCYEAR]; $bysetposMold = $wdate[util::$LCMONTH]; } elseif( isset( $recur[util::$FREQ] ) && ( self::$WEEKLY == $recur[util::$FREQ] )) { $weekno = (int) date( self::$W, mktime( 0, 0, $wkst, $wdate[util::$LCMONTH], $wdate[util::$LCDAY], $wdate[util::$LCYEAR] )); if( $bysetposWold != $weekno ) { $bysetposWold = $weekno; $bysetpos = true; } } elseif( isset( $recur[util::$FREQ] ) && ( self::$DAILY == $recur[util::$FREQ] ) && (( $bysetposYold != $wdate[util::$LCYEAR] ) || ( $bysetposMold != $wdate[util::$LCMONTH] ) || ( $bysetposDold != $wdate[util::$LCDAY] ))) { $bysetpos = true; $bysetposYold = $wdate[util::$LCYEAR]; $bysetposMold = $wdate[util::$LCMONTH]; $bysetposDold = $wdate[util::$LCDAY]; } if( $bysetpos ) { if( isset( $recur[util::$BYWEEKNO] )) { $bysetposarr1 = & $bysetposw1; $bysetposarr2 = & $bysetposw2; } else { $bysetposarr1 = & $bysetposymd1; $bysetposarr2 = & $bysetposymd2; } foreach( $recur[util::$BYSETPOS] as $ix ) { if( 0 > $ix ) // both positive and negative BYSETPOS allowed $ix = ( count( $bysetposarr1 ) + $ix + 1); $ix--; if( isset( $bysetposarr1[$ix] )) { if( $fcnStartYMD <= $bysetposarr1[$ix] ) { // only output within period // $testweekno = (int) date( $W, mktime( 0, 0, $wkst, (int) substr( $bysetposarr1[$ix], 4, 2 ), (int) substr( $bysetposarr1[$ix], 6, 2 ), (int) substr( $bysetposarr1[$ix], 0, 3 ))); // test ### // echo " testYMD (weekno)=$bysetposarr1[$ix] ($testweekno)"; // test ### $result[$bysetposarr1[$ix]] = true; } $recurCount++; } if( isset( $recur[util::$COUNT] ) && ( $recurCount >= $recur[util::$COUNT] )) break; } // echo "
\n"; // test ### $bysetposarr1 = $bysetposarr2; $bysetposarr2 = []; } // end if( $bysetpos ) } // end if( $updateOK && isset( $recur['BYSETPOS'] )) } // end while( true ) // echo 'output='.str_replace( [PHP_EOL, ' '], '', var_export( $result, true ))."
\n"; // test ### } /** * Checking BYDAY (etc) hits, recur2date help function * * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.6.12 - 2011-01-03 * @param array $BYvalue * @param int $upValue * @param int $downValue * @return bool * @access private * @static */ private static function recurBYcntcheck( $BYvalue, $upValue, $downValue ) { if( is_array( $BYvalue ) && ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue ))) return true; elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue )) return true; else return false; } /** * (re-)Calculate internal index, recur2date help function * * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.6.12 - 2011-01-03 * @param string $freq * @param array $date * @param int $wkst * @return bool * @access private * @static */ private static function recurIntervalIx( $freq, $date, $wkst ) { /* create interval index */ switch( $freq ) { case self::$YEARLY : $intervalix = $date[util::$LCYEAR]; break; case self::$MONTHLY : $intervalix = $date[util::$LCYEAR] . util::$MINUS . $date[util::$LCMONTH]; break; case self::$WEEKLY : $intervalix = (int) date( self::$W, mktime( 0, 0, $wkst, (int) $date[util::$LCMONTH], (int) $date[util::$LCDAY], (int) $date[util::$LCYEAR] )); break; case self::$DAILY : default: $intervalix = $date[util::$LCYEAR] . util::$MINUS . $date[util::$LCMONTH] . util::$MINUS . $date[util::$LCDAY]; break; } return $intervalix; } /** * Return updated date, array and timpstamp * * @param array $date date to step * @param string $dateYMD date YMD * @param array $step default array( util::$LCDAY => 1 ) * @return void * @access private * @static */ private static function stepdate( & $date, & $dateYMD, $step=null ) { static $t = 't'; if( is_null( $step )) $step = [util::$LCDAY => 1]; if( ! isset( $date[util::$LCHOUR] )) $date[util::$LCHOUR] = 0; if( ! isset( $date[util::$LCMIN] )) $date[util::$LCMIN] = 0; if( ! isset( $date[util::$LCSEC] )) $date[util::$LCSEC] = 0; if( isset( $step[util::$LCDAY] )) $mcnt = date( $t, mktime( (int) $date[util::$LCHOUR], (int) $date[util::$LCMIN], (int) $date[util::$LCSEC], (int) $date[util::$LCMONTH], (int) $date[util::$LCDAY], (int) $date[util::$LCYEAR] )); foreach( $step as $stepix => $stepvalue ) $date[$stepix] += $stepvalue; if( isset( $step[util::$LCMONTH] )) { if( 12 < $date[util::$LCMONTH] ) { $date[util::$LCYEAR] += 1; $date[util::$LCMONTH] -= 12; } } elseif( isset( $step[util::$LCDAY] )) { if( $mcnt < $date[util::$LCDAY] ) { $date[util::$LCDAY] -= $mcnt; $date[util::$LCMONTH] += 1; if( 12 < $date[util::$LCMONTH] ) { $date[util::$LCYEAR] += 1; $date[util::$LCMONTH] -= 12; } } } $dateYMD = sprintf( util::$YMD, (int) $date[util::$LCYEAR], (int) $date[util::$LCMONTH], (int) $date[util::$LCDAY] ); } /** * Return initiated $daycnts * * @param array $wdate * @param array $recur * @param int $wkst * @return array * @access private * @static */ private static function initDayCnts( array $wdate, array $recur, $wkst ) { $daycnts = []; $yeardaycnt = []; $yeardays = 0; foreach( self::$DAYNAMES as $dn ) $yeardaycnt[$dn] = 0; for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters $daycnts[$m] = []; $weekdaycnt = []; foreach( self::$DAYNAMES as $dn ) $weekdaycnt[$dn] = 0; $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate[util::$LCYEAR] )); for( $d = 1; $d <= $mcnt; $d++ ) { $daycnts[$m][$d] = []; if( isset( $recur[util::$BYYEARDAY] )) { $yeardays++; $daycnts[$m][$d][self::$YEARCNT_UP] = $yeardays; } if( isset( $recur[util::$BYDAY] )) { $day = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate[util::$LCYEAR] )); $day = self::$DAYNAMES[$day]; $daycnts[$m][$d][util::$DAY] = $day; $weekdaycnt[$day]++; $daycnts[$m][$d][self::$MONTHDAYNO_UP] = $weekdaycnt[$day]; $yeardaycnt[$day]++; $daycnts[$m][$d][self::$YEARDAYNO_UP] = $yeardaycnt[$day]; } if( isset( $recur[util::$BYWEEKNO] ) || ( $recur[util::$FREQ] == self::$WEEKLY )) $daycnts[$m][$d][self::$WEEKNO_UP] = (int) date( self::$W, mktime( 0, 0, $wkst, $m, $d, $wdate[util::$LCYEAR] )); } // end for( $d = 1; $d <= $mcnt; $d++ ) } // end for( $m = 1; $m <= 12; $m++ ) $daycnt = 0; $yeardaycnt = []; if( isset( $recur[util::$BYWEEKNO] ) || ( $recur[util::$FREQ] == self::$WEEKLY )) { $weekno = null; for( $d=31; $d > 25; $d-- ) { // get last weekno for year if( ! $weekno ) $weekno = $daycnts[12][$d][self::$WEEKNO_UP]; elseif( $weekno < $daycnts[12][$d][self::$WEEKNO_UP] ) { $weekno = $daycnts[12][$d][self::$WEEKNO_UP]; break; } } } for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters $weekdaycnt = []; foreach( self::$DAYNAMES as $dn ) $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; $monthcnt = 0; $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate[util::$LCYEAR] )); for( $d = $mcnt; $d > 0; $d-- ) { if( isset( $recur[util::$BYYEARDAY] )) { $daycnt -= 1; $daycnts[$m][$d][self::$YEARCNT_DOWN] = $daycnt; } if( isset( $recur[util::$BYMONTHDAY] )) { $monthcnt -= 1; $daycnts[$m][$d][self::$MONTHCNT_DOWN] = $monthcnt; } if( isset( $recur[util::$BYDAY] )) { $day = $daycnts[$m][$d][util::$DAY]; $weekdaycnt[$day] -= 1; $daycnts[$m][$d][self::$MONTHDAYNO_DOWN] = $weekdaycnt[$day]; $yeardaycnt[$day] -= 1; $daycnts[$m][$d][self::$YEARDAYNO_DOWN] = $yeardaycnt[$day]; } if( isset( $recur[util::$BYWEEKNO] ) || ( $recur[util::$FREQ] == self::$WEEKLY )) $daycnts[$m][$d][self::$WEEKNO_DOWN] = ( $daycnts[$m][$d][self::$WEEKNO_UP] - $weekno - 1 ); } } // end for( $m = 12; $m > 0; $m-- ) return $daycnts; } /** * Return a reformatted input date * * @param mixed $aDate * @access private * @static */ private static function reFormatDate( & $aDate ) { static $YMDHIS2 = 'Y-m-d H:i:s'; switch( true ) { case ( is_string( $aDate )) : util::strDate2arr( $aDate ); break; case ( util::isDateTimeClass( $aDate )) : $aDate = $aDate->format( $YMDHIS2 ); util::strDate2arr( $aDate ); break; default : break; } foreach( $aDate as $k => $v ) { if( ctype_digit( $v )) $aDate[$k] = (int) $v; } } }