* @copyright 2007-2022 Kjell-Inge Gustafsson, kigkonsult, All rights reserved * @link https://kigkonsult.se * @license Subject matter of licence is the software iCalcreator. * The above copyright, link, package and version notices, * this licence notice and the invariant [rfc5545] PRODID result use * as implemented and invoked in iCalcreator shall be included in * all copies or substantial portions of the iCalcreator. * * iCalcreator is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with iCalcreator. If not, see . */ namespace Kigkonsult\Icalcreator; use Kigkonsult\Icalcreator\Util\DateTimeFactory; use Kigkonsult\Icalcreator\Util\RecurFactory2; use DateTime; use Exception; /** * class RecurTest, testing selectComponents * * @since 2.27.20 - 2019-05-20 */ class RecurYearTest extends RecurBaseTest { /** * recurYearlyTest111x provider * * @return mixed[] * @throws Exception */ public function recurYearlyTest111xProvider() : array { $dataArr = []; $dataSetNo = 0; $DATASET = 'DATASET'; $interval = 1; $count = 10; for( $ix = 111; $ix <= 112; $ix++ ) { $time = microtime( true ); $start = DateTimeFactory::factory( '20190101T0900', 'Europe/Stockholm' ); $year = (int)$start->format( 'Y' ); $month = (int)$start->format( 'm' ); $day = (int)$start->format( 'd' ); $end = ( clone $start )->modify( '20 years' ); $expects = []; $x = 1; while( $x < $count ) { $year += $interval; $Ymd = sprintf( '%04d%02d%02d', $year, $month, $day ); $expects[] = $Ymd; ++$x; } $execTime = microtime( true ) - $time; $dataArr[] = [ $ix . '-' . $interval, $start, $end, [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::INTERVAL => $interval, IcalInterface::COUNT => $count, $DATASET => $dataSetNo++ ], $expects, $execTime ]; ++$interval; } return $dataArr; } /** * Testing recur2date Yearly simple * * @test * @dataProvider recurYearlyTest111xProvider * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @param float $prepTime * @throws Exception */ public function recurYearlyTest111x( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects, float $prepTime ) : void { $this->recurYearlyTest1X( $case, $start, $end, $recur, $expects, $prepTime ); } /** * recurYearlyTest23 provider * * @return mixed[] * @throws Exception */ public function recurYearlyTest23Provider() : array { $dataArr = []; $dataSetNo = 0; $DATASET = 'DATASET'; // rfc example 23 - exact (no interval) $dataArr[] = [ '19-23-0-0-0', DateTimeFactory::factory( '19970610T0900', 'Europe/Stockholm' ), DateTimeFactory::factory( '20040610T0900', 'Europe/Stockholm' ), [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::COUNT => 10, IcalInterface::BYMONTH => [ 6, 7 ], $DATASET => $dataSetNo, 'MRANGE' => [ 6, 7 ] ], [ 19970710, 19980610, 19980710, 19990610, 19990710, 20000610, 20000710, 20010610, 20010710 ], 0,0, ]; // rfc example 23 - with interval for-loop '19-23-...' $count = 10; $mRange = []; for( $ix1 = 1; $ix1 < 5; $ix1++ ) { $interval = 0; for( $ix2 = 1; $ix2 <= 10; $ix2++ ) { ++$interval; $mRange[] = array_rand( array_flip( range( 1, 12 ))); sort( $mRange ); $mRange = array_unique( $mRange, SORT_NUMERIC ); $time = microtime( true ); // $start = DateTimeFactory::factory( '20190101T0900', 'Europe/Stockholm' ); $start = DateTimeFactory::factory( '19970610T0900', 'Europe/Stockholm' ); $startYmd = $start->format( 'Ymd' ); $end = ( clone $start )->setDate( ((int) $start->format( 'Y' ) + ( 10 + $interval )), (int) $start->format( 'm' ), (int) $start->format( 'd' ) ); $endYmd = $end->format( 'Ymd' ); $expects = []; $x = 1; $wDate = clone $start; $wDate = $wDate->setDate( (int) $wDate->format( 'Y' ), 1, (int) $wDate->format( 'd' ) ); $currYear = (int) $wDate->format( 'Y' ); while( $x < $count ) { if( $currYear !== (int) $wDate->format( 'Y' )) { $wDate = $wDate->setDate( ((int) $wDate->format( 'Y' ) + $interval ), 1, (int) $wDate->format( 'd' ) ); $currYear = (int) $wDate->format( 'Y' ); } if( $endYmd < $wDate->format( 'Ymd' )) { break; } if( $startYmd < $wDate->format( 'Ymd' )) { if( in_array( (int)$wDate->format( 'm' ), $mRange, true ) ) { $expects[] = $wDate->format( 'Ymd' ); ++$x; } if( 12 === (int) $wDate->format( 'm' )) { $currYear = -1; continue; } } $wDate = $wDate->setDate( (int) $wDate->format( 'Y' ), ((int) $wDate->format( 'm' ) + 1 ), (int) $wDate->format( 'd' ) ); } // end while $execTime = microtime( true ) - $time; $dataArr[] = [ '19-23-' . $ix1 . '-' . $ix2 . '-' . $interval, $start, $end, [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::INTERVAL => $interval, IcalInterface::COUNT => $count, IcalInterface::BYMONTH => $mRange, $DATASET => $dataSetNo++, 'MRANGE' => implode( ',', $mRange ) ], $expects, $execTime, ]; } // end for... $x2 } // end for... $x1 return $dataArr; } /** * Testing recur2date Yearly , rfc example 23 - with interval for-loop '19-23-...' * * Test RecurFactory::recurYearly1 * * @test * @dataProvider recurYearlyTest23Provider * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @param float $prepTime * @throws Exception */ public function recurYearlyTest23( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects, float $prepTime ) : void { $this->recurYearlyTest1X( $case, $start, $end, $recur, $expects, $prepTime ); } /** * recurYearlyTest23e provider, rfc example 23 -Extended, both byMonth and byMonthDay * * @return mixed[] * @throws Exception */ public function recurYearlyTest23eProvider() : array { $dataArr = []; $dataSetNo = 0; $DATASET = 'DATASET'; // rfc example 23 -Extended, both byMonth and byMonthDay $start = DateTimeFactory::factory( '20190101T0900', 'Europe/Stockholm' ); $end = ( clone $start )->modify('+10 years' ); $count = 20; $mRange = [ 1 ]; // month $dRange = []; // days in month $baseDays = [ 4, 8, 12, 16, -16, -12, -8, -4 ]; for( $ix1 = 1; $ix1 < 5; $ix1++ ) { $interval = 1; for( $ix2 = 1; $ix2 <= 2; $ix2++ ) { $mRange[] = array_rand( array_flip( range( 4, 12 ))); sort( $mRange ); $mRange = array_values( array_unique( $mRange )); $dKey = array_rand( $baseDays ); $dRange[] = $baseDays[$dKey]; sort( $dRange ); $dRange = array_values( array_unique( $dRange )); $time = microtime( true ); $startYmd = $start->format( 'Ymd' ); $startYm = $start->format( 'Ym' ); $endYmd = $end->format( 'Ymd' ); $expects = []; $x = 1; $wDate = clone $start; $currYear = $year = (int) $wDate->format( 'Y' ); $mx = 0; $month = $mRange[$mx]; $day = (int) $wDate->format( 'd' ); $wDate->setDate( $year, $month, $day ); $currMonth = $month; while(( $x < $count ) && ( $endYmd > $wDate->format( 'Ymd' ))) { // if( 4000 < ++$y ) break; if( $currYear !== (int) $wDate->format( 'Y' )) { $year += $interval; $currYear = $year; $mx = 0; $currMonth = $month = $mRange[$mx]; } // end if if( $currMonth !== $month ) { ++$mx; if( ! isset( $mRange[$mx] )) { $currYear = -1; continue; } $currMonth = $month = $mRange[$mx]; } // end if $wDate->setDate( $year, $month, $day ); if( $endYmd < $wDate->format( 'Ymd' )) { break; } if( $startYm > $wDate->format( 'Ym' )) { $currMonth = -1; continue; } if( in_array( $month, $mRange, true ) ) { // bort ?? $xDate = clone $wDate; foreach( RecurFactory2::getMonthDaysFromByMonthDayList( (int) $wDate->format( 't' ), $dRange ) as $monthDay ) { if( $x >= $count ) { break 2; } $xDate->setDate( (int) $wDate->format( 'Y' ), (int) $wDate->format( 'm' ), $monthDay ); $Ymd = $xDate->format( 'Ymd' ); if( $startYmd >= $Ymd ) { continue; } if( $endYmd < $Ymd ) { break 2; } $expects[] = $Ymd; ++$x; } // end foreach } // end if ... in mRange $currMonth = -1; } // end while $execTime = microtime( true ) - $time; $dataArr[] = [ '19-23e' . $ix1 . '-' . $ix2 . '-' . $interval, $start, $end, [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::INTERVAL => $interval, IcalInterface::COUNT => $count, IcalInterface::BYMONTH => $mRange, IcalInterface::BYMONTHDAY => $dRange, $DATASET => $dataSetNo++, 'MRANGE' => implode( ',', $mRange ) ], $expects, $execTime, ]; } // end for... $x2 } // end for... $x1 return $dataArr; } /** * Testing recur2date Yearly , rfc example 23 -Extended, both byMonth and byMonthDay * * @test * @dataProvider recurYearlyTest23eProvider * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @param float $prepTime * @throws Exception */ public function recurYearlyTest23e( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects, float $prepTime ) : void { $this->recurYearlyTest1X( $case, $start, $end, $recur, $expects, $prepTime ); } /** * recurYearlyTest23l provider, rfc example 23 changed date and limited by INTERVAL 2 and byMonthDay [ -20, -10 ] * * @return mixed[] * @throws Exception */ public function recurYearlyTest23lProvider() : array { $dataArr = []; $dataSetNo = 0; $DATASET = 'DATASET'; // rfc example 23 changed date and limited by INTERVAL 2 and byMonthDay [ -20, -10 ] $start = DateTimeFactory::factory( '20200801T0900', 'Europe/Stockholm' ); $end = ( clone $start )->modify('+12 years' ); $dataArr[] = [ '19-23l', $start, $end, [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::INTERVAL => 2, IcalInterface::COUNT => 10, IcalInterface::BYMONTHDAY => [ -15, -2 ], $DATASET => $dataSetNo++, ], [ 20200817, 20200830, 20220817, 20220830, 20240817, 20240830, 20260817, 20260830, 20280817 ], 0.0, ]; return $dataArr; } /** * Testing recur2date Yearly, rfc example 23 changed date and limited by INTERVAL 2 and byMonthDay [ -20, -10 ] * * @test * @dataProvider recurYearlyTest23lProvider * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @param float $prepTime * @throws Exception */ public function recurYearlyTest23l( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects, float $prepTime ) : void { $this->recurYearlyTest1X( $case, $start, $end, $recur, $expects, $prepTime ); } /** * Testing recur2date Yearly * * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @param float $prepTime * @throws Exception */ public function recurYearlyTest1X( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects, float $prepTime ) : void { $saveStartDate = clone $start; if( ! isset( $recur[IcalInterface::INTERVAL] )) { $recur[IcalInterface::INTERVAL] = 1; } // error_log('' ); // test ### // error_log( __FUNCTION__ . ' start ' . $case ); // test ### $strCase = str_pad( $case, 12 ); if( '19-23l' === $case ) { $result = array_flip( $expects ); if( defined( 'DISPRECUR' ) && ( '1' === DISPRECUR )) { echo $strCase . 'expects : ' . implode( ' - ', array_keys( $result ) ) . PHP_EOL; // test ### } } else { $result = $this->recur2dateTest( // return old $case, $start, $end, $recur, $expects, $prepTime ); } $recurDisp = str_replace( [PHP_EOL, ' ' ], '', var_export( $recur, true )); if( ! RecurFactory2::isRecurYearly1( $recur )) { if( defined( 'DISPRECUR' ) && ( '1' === DISPRECUR )) { echo $strCase . ' NOT isRecurYearly1 ' . $recurDisp . PHP_EOL; } $this->fail(); } $time = microtime( true ); $resultX = RecurFactory2::recurYearly1( $recur, $start, clone $start, $end ); $execTime = microtime( true ) - $time; if( defined( 'DISPRECUR' ) && ( '1' === DISPRECUR )) { echo $strCase . 'year smpl1 time:' . number_format( $execTime, 6 ) . ' : ' . implode( ' - ', array_keys( $resultX ) ) . PHP_EOL; // test ### echo $recurDisp . ' start ' . $start->format( 'Ymd' ) . ' end ' . $end->format( 'Ymd' ) . PHP_EOL; // test ### } $endFormat = is_array( $end ) ? implode( '-', $end ) : $end->format( 'Ymd' ); $this->assertEquals( array_keys( $result ), // exp, old array_keys( $resultX ), // new, actual sprintf( self::$ERRFMT, __FUNCTION__, $case . ' old -> new ', $saveStartDate->format( 'Ymd' ), $endFormat, $recurDisp ) ); } /** * recurYearlyTest2X provider * * @return mixed[] * @throws Exception */ public function recurYearlyTest2XProvider() : array { $dataArr = []; $dataSetNo = 0; $DATASET = 'DATASET'; // yearly in june, third TU/WE/TH in month, forever $start = DateTimeFactory::factory( '20200101T090000', 'Europe/Stockholm'); $wDate = clone $start; $dataArr[] = [ '2001', $start, $wDate->modify( 10 . ' year' ), // end [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::BYMONTH => 6, IcalInterface::BYDAY => [ [ IcalInterface::DAY => IcalInterface::TU ], [ IcalInterface::DAY => IcalInterface::WE ], [ IcalInterface::DAY => IcalInterface::TH ], ], IcalInterface::BYSETPOS => -3, $DATASET => $dataSetNo++ ], [ 20200624,20210624,20220628,20230627,20240625,20250624,20260624,20270624,20280627,20290626 ] ]; // neotsn Thanksgiving event - 4th Thursday of every November - Yearly - same in recurMonthly2Test by MONTHLY $start = DateTimeFactory::factory( '20201126T113000', 'America/Chicago'); $wDate = clone $start; $dataArr[] = [ 'neotsn', $start, $wDate->modify( 10 . ' year' ), // end [ IcalInterface::FREQ => IcalInterface::YEARLY, IcalInterface::BYMONTH => 11, IcalInterface::BYDAY => [ [ IcalInterface::DAY => IcalInterface::TH ], ], IcalInterface::BYSETPOS => 4, $DATASET => $dataSetNo++ ], [ 20211125,20221124,20231123,20241128,20251127,20261126,20271125,20281123,20291122 ] ]; return $dataArr; } /** * Testing recurMonthlyYearly3 - YEARLY + BYMONTH + BYDAY * * @test * @dataProvider recurYearlyTest2XProvider * @param string $case * @param DateTime $start * @param DateTime|mixed[] $end * @param mixed[] $recur * @param mixed[] $expects * @throws Exception */ public function recurYearly2XTest( string $case, DateTime $start, DateTime | array $end, array $recur, array $expects ) : void { $saveStartDate = clone $start; if( ! isset( $recur[IcalInterface::INTERVAL] )) { $recur[IcalInterface::INTERVAL] = 1; } $strCase = str_pad( $case, 12 ); $recurDisp = str_replace( [PHP_EOL, ' ' ], '', var_export( $recur, true )); if( ! RecurFactory2::isRecurYearly2( $recur )) { if( defined( 'DISPRECUR' ) && ( '1' === DISPRECUR )) { echo $strCase . ' NOT isRecurYearly2 ' . $recurDisp . PHP_EOL; } $this->fail(); } // end if $time = microtime( true ); $resultX = RecurFactory2::recurMonthlyYearly3( $recur, $start, clone $start, $end ); $execTime = microtime( true ) - $time; if( defined( 'DISPRECUR' ) && ( '1' === DISPRECUR )) { echo $strCase . 'year smpl2 time:' . number_format( $execTime, 6 ) . ' : ' . implode( ' - ', array_keys( $resultX ) ) . PHP_EOL; // test ### echo $recurDisp . PHP_EOL; // test ### } $this->assertEquals( $expects, array_keys( $resultX ), sprintf( self::$ERRFMT, __FUNCTION__, $case . '-21', $saveStartDate->format( 'Ymd' ), $end->format( 'Ymd' ), PHP_EOL . $recurDisp . PHP_EOL . 'exp : ' . implode( ',', $expects ) . PHP_EOL . 'got : ' . implode( ',', array_keys( $resultX )) ) ); } }