IteratorTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of the Carbon package.
  5. *
  6. * (c) Brian Nesbitt <brian@nesbot.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Tests\CarbonPeriod;
  12. use Carbon\Carbon;
  13. use Carbon\CarbonInterval;
  14. use Carbon\CarbonPeriod;
  15. use Generator;
  16. use Tests\AbstractTestCase;
  17. use Tests\CarbonPeriod\Fixtures\CarbonPeriodFactory;
  18. class IteratorTest extends AbstractTestCase
  19. {
  20. protected $iterationLimit = 100;
  21. public function testKeyAndCurrentAreCorrectlyInstantiated()
  22. {
  23. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  24. $this->assertSame(0, $period->key());
  25. $this->assertInstanceOfCarbon($period->current());
  26. $this->assertSame('2012-07-04 00:00:00', $period->current()->format('Y-m-d H:i:s'));
  27. $this->assertTrue($period->valid());
  28. }
  29. public function testValidIsCorrectlyInstantiated()
  30. {
  31. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  32. $this->assertTrue($period->valid());
  33. }
  34. public function testCurrentIsAlwaysCarbonInstance()
  35. {
  36. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  37. foreach ($period as $key => $current) {
  38. $this->assertInstanceOfCarbon($current);
  39. $this->assertEquals($current, $period->current());
  40. $this->assertEquals($current, $period->current);
  41. }
  42. }
  43. public function testKeysAreSequential()
  44. {
  45. $keys = [];
  46. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  47. foreach ($period as $key => $current) {
  48. $this->assertIsInt($keys[] = $key);
  49. $this->assertSame($key, $period->key());
  50. }
  51. $this->assertSame(array_keys($keys), $keys);
  52. }
  53. public function testElementsInLoopAreAlwaysValid()
  54. {
  55. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  56. foreach ($period as $key => $current) {
  57. $this->assertTrue($period->valid());
  58. }
  59. }
  60. public function testKeyAndCurrentAreCorrectlyIterated()
  61. {
  62. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  63. $period->next();
  64. $this->assertSame(1, $period->key());
  65. $this->assertSame('2012-07-10 00:00:00', $period->current()->format('Y-m-d H:i:s'));
  66. $this->assertTrue($period->valid());
  67. }
  68. public function testKeyAndCurrentAreCorrectlyRewound()
  69. {
  70. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  71. $period->next();
  72. $period->rewind();
  73. $this->assertSame(0, $period->key());
  74. $this->assertSame('2012-07-04 00:00:00', $period->current()->format('Y-m-d H:i:s'));
  75. $this->assertTrue($period->valid());
  76. }
  77. public function testKeyAndCurrentAreNullAfterIteration()
  78. {
  79. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  80. foreach ($period as $key => $current) {
  81. //
  82. }
  83. $this->assertNull($period->key());
  84. $this->assertNull($period->current());
  85. $this->assertFalse($period->valid());
  86. }
  87. /**
  88. * @dataProvider dataForIterateBackwardsArguments
  89. */
  90. public function testIterateBackwards($arguments, $expected)
  91. {
  92. $periodClass = $this->periodClass;
  93. $period = $periodClass::create(...$arguments);
  94. $interval = new CarbonInterval('P3D');
  95. $interval->invert = 1;
  96. $period = $period->setDateInterval($interval);
  97. $this->assertSame(
  98. $this->standardizeDates($expected),
  99. $this->standardizeDates($period)
  100. );
  101. }
  102. public static function dataForIterateBackwardsArguments(): Generator
  103. {
  104. yield [
  105. ['2015-10-15', '2015-10-06'],
  106. ['2015-10-15', '2015-10-12', '2015-10-09', '2015-10-06'],
  107. ];
  108. yield [
  109. ['2015-10-15', '2015-10-06', CarbonPeriod::EXCLUDE_START_DATE],
  110. ['2015-10-12', '2015-10-09', '2015-10-06'],
  111. ];
  112. yield [
  113. ['2015-10-15', '2015-10-06', CarbonPeriod::EXCLUDE_END_DATE],
  114. ['2015-10-15', '2015-10-12', '2015-10-09'],
  115. ];
  116. yield [
  117. ['2015-10-15', '2015-10-06', CarbonPeriod::EXCLUDE_START_DATE | CarbonPeriod::EXCLUDE_END_DATE],
  118. ['2015-10-12', '2015-10-09'],
  119. ];
  120. yield [
  121. ['2015-10-15', 3],
  122. ['2015-10-15', '2015-10-12', '2015-10-09'],
  123. ];
  124. }
  125. public function testChangingParametersShouldNotCauseInfiniteLoop()
  126. {
  127. $periodClass = $this->periodClass;
  128. $period = $periodClass::create()
  129. ->setStartDate($start = '2012-07-01')
  130. ->setEndDate($end = '2012-07-20')
  131. ->setDateInterval($interval = 'P1D')
  132. ->setRecurrences($recurrences = 10)
  133. ->setOptions($options = $periodClass::EXCLUDE_START_DATE | $periodClass::EXCLUDE_END_DATE)
  134. ->addFilter($filter = function () {
  135. return true;
  136. });
  137. $counter = 0;
  138. foreach ($period as $current) {
  139. if (++$counter >= $this->iterationLimit) {
  140. break;
  141. }
  142. $period->removeFilter($filter)
  143. ->prependFilter($filter)
  144. ->setFilters([])
  145. ->setStartDate($start)
  146. ->setEndDate($end)
  147. ->invertDateInterval()
  148. ->setDateInterval($interval)
  149. ->setRecurrences($recurrences)
  150. ->setOptions($options)
  151. ->resetFilters()
  152. ->addFilter($filter);
  153. }
  154. $this->assertLessThan($this->iterationLimit, $counter, 'Changing parameters during the iteration caused an infinite loop.');
  155. }
  156. public function testChangeEndDateDuringIteration()
  157. {
  158. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  159. $results = [];
  160. foreach ($period as $key => $current) {
  161. $results[] = sprintf('%s => %s', $key, $current->toDateString());
  162. if ($current->toDateString() === '2012-07-16') {
  163. $period = $period->setEndDate($current);
  164. // Note: Current is no longer valid, because it is now equal to end, which is excluded.
  165. $this->assertNull($period->key());
  166. $this->assertNull($period->current());
  167. $this->assertFalse($period->valid());
  168. }
  169. if (\count($results) >= $this->iterationLimit) {
  170. $this->fail('Infinite loop detected when traversing the period.');
  171. }
  172. }
  173. $this->assertSame(
  174. ['0 => 2012-07-04', '1 => 2012-07-10', '2 => 2012-07-16'],
  175. $results
  176. );
  177. }
  178. public function testKeepIncreasingRecurrencesDuringIteration()
  179. {
  180. $period = new CarbonPeriod('2012-07-01', $recurrences = 1);
  181. $results = [];
  182. foreach ($period as $key => $current) {
  183. $results[] = sprintf('%s => %s', $key, $current->toDateString());
  184. if ($recurrences < 4) {
  185. $period->setRecurrences(++$recurrences);
  186. // Note: Current is still valid, because we simply extended the period.
  187. $this->assertSame($key, $period->key());
  188. $this->assertEquals($current, $period->current());
  189. $this->assertTrue($period->valid());
  190. }
  191. if (\count($results) >= $this->iterationLimit) {
  192. $this->fail('Infinite loop detected when traversing the period.');
  193. }
  194. }
  195. $this->assertSame(
  196. ['0 => 2012-07-01', '1 => 2012-07-02', '2 => 2012-07-03', '3 => 2012-07-04'],
  197. $results
  198. );
  199. }
  200. public function testChangeStartDateDuringIteration()
  201. {
  202. $period = new CarbonPeriod('2012-07-01', '2012-07-04');
  203. $results = [];
  204. $newStart = new Carbon('2012-07-03');
  205. foreach ($period as $key => $current) {
  206. $results[] = sprintf('%s => %s', $key, $current->toDateString());
  207. if ($current < $newStart) {
  208. $period->setStartDate($newStart);
  209. // Note: Current is still valid, because start date is used only for initialization.
  210. $this->assertEquals($key, $period->key());
  211. $this->assertEquals($current, $period->current());
  212. $this->assertTrue($period->valid());
  213. }
  214. if (\count($results) >= $this->iterationLimit) {
  215. $this->fail('Infinite loop detected when traversing the period.');
  216. }
  217. }
  218. $this->assertSame(
  219. // Note: Results are not affected, because start date is used only for initialization.
  220. ['0 => 2012-07-01', '1 => 2012-07-02', '2 => 2012-07-03', '3 => 2012-07-04'],
  221. $results
  222. );
  223. }
  224. public function testChangeDateIntervalDuringIteration()
  225. {
  226. $period = new CarbonPeriod('2012-07-01', 3);
  227. $results = [];
  228. foreach ($period as $key => $current) {
  229. $results[] = sprintf('%s => %s', $key, $current->toDateString());
  230. $period->setDateInterval('P3D');
  231. // Note: Current is still valid, because changed interval changes only subsequent items.
  232. $this->assertSame($key, $period->key());
  233. $this->assertEquals($current, $period->current());
  234. $this->assertTrue($period->valid());
  235. if (\count($results) >= $this->iterationLimit) {
  236. $this->fail('Infinite loop detected when traversing the period.');
  237. }
  238. }
  239. $this->assertSame(
  240. ['0 => 2012-07-01', '1 => 2012-07-04', '2 => 2012-07-07'],
  241. $results
  242. );
  243. }
  244. public function testValidateOncePerIteration()
  245. {
  246. $period = CarbonPeriodFactory::withCounter($this->periodClass, $counter);
  247. $period->key();
  248. $period->current();
  249. $period->valid();
  250. $this->assertSame(1, $counter);
  251. $period->next();
  252. $this->assertSame(2, $counter);
  253. $period->key();
  254. $period->current();
  255. $period->valid();
  256. $this->assertSame(2, $counter);
  257. }
  258. public function testInvalidateCurrentAfterChangingParameters()
  259. {
  260. $periodClass = $this->periodClass;
  261. $period = $periodClass::create('2012-10-01');
  262. $this->assertInstanceOfCarbon($period->current());
  263. $period = $period->addFilter($periodClass::END_ITERATION);
  264. $this->assertNull($period->current());
  265. }
  266. public function testTraversePeriodDynamically()
  267. {
  268. $period = CarbonPeriodFactory::withEvenDaysFilter($this->periodClass);
  269. $results = [];
  270. while ($current = $period->current()) {
  271. $results[] = $current;
  272. $period->next();
  273. if (\count($results) >= $this->iterationLimit) {
  274. $this->fail('Infinite loop detected when traversing the period.');
  275. }
  276. }
  277. $this->assertSame(
  278. $this->standardizeDates(['2012-07-04', '2012-07-10', '2012-07-16']),
  279. $this->standardizeDates($results)
  280. );
  281. }
  282. public function testExtendCompletedIteration()
  283. {
  284. $periodClass = $this->periodClass;
  285. $period = $periodClass::create('2018-10-10', '2018-10-11');
  286. $results = [];
  287. while ($period->valid()) {
  288. $results[] = $period->current();
  289. $period->next();
  290. }
  291. $period = $period->setEndDate('2018-10-13');
  292. while ($period->valid()) {
  293. $results[] = $period->current();
  294. $period->next();
  295. }
  296. $this->assertSame(
  297. $this->standardizeDates(['2018-10-10', '2018-10-11', '2018-10-12', '2018-10-13']),
  298. $this->standardizeDates($results)
  299. );
  300. }
  301. public function testRevalidateCurrentAfterChangeOfParameters()
  302. {
  303. $periodClass = $this->periodClass;
  304. $period = $periodClass::create()->setStartDate($start = new Carbon('2018-10-28'));
  305. $this->assertEquals($start, $period->current());
  306. $this->assertNotSame($start, $period->current());
  307. $period = $period->addFilter($excludeStart = function ($date) use ($start) {
  308. return $date != $start;
  309. });
  310. $this->assertNull($period->current());
  311. $period = $period->removeFilter($excludeStart);
  312. $this->assertEquals($start, $period->current());
  313. $this->assertNotSame($start, $period->current());
  314. }
  315. public function testRevalidateCurrentAfterEndOfIteration()
  316. {
  317. $periodClass = $this->periodClass;
  318. $period = $periodClass::create()->setStartDate($start = new Carbon('2018-10-28'));
  319. $this->assertEquals($start, $period->current());
  320. $this->assertNotSame($start, $period->current());
  321. $period = $period->addFilter($periodClass::END_ITERATION);
  322. $this->assertNull($period->current());
  323. $period = $period->removeFilter($periodClass::END_ITERATION);
  324. $this->assertEquals($start, $period->current());
  325. $this->assertNotSame($start, $period->current());
  326. }
  327. public function testChangeStartDateBeforeIteration()
  328. {
  329. $periodClass = $this->periodClass;
  330. $period = $periodClass::create(new Carbon('2018-10-05'), 3)
  331. ->setStartDate(new Carbon('2018-10-13'))
  332. ->toggleOptions($periodClass::EXCLUDE_START_DATE, true);
  333. $this->assertEquals(new Carbon('2018-10-14'), $period->current());
  334. }
  335. public function testChangeStartDateAfterStartedIteration()
  336. {
  337. $periodClass = $this->periodClass;
  338. $period = $periodClass::create(new Carbon('2018-10-05'), 3);
  339. $current = $period->current();
  340. $period->toggleOptions($periodClass::EXCLUDE_START_DATE, true);
  341. $period->setStartDate(new Carbon('2018-10-13'));
  342. $this->assertEquals($current, $period->current());
  343. }
  344. public function testInvertDateIntervalDuringIteration()
  345. {
  346. $periodClass = $this->periodClass;
  347. $period = new $periodClass('2018-04-11', 5);
  348. $results = [];
  349. foreach ($period as $key => $date) {
  350. $results[] = $date;
  351. if ($key === 2) {
  352. $period->invertDateInterval();
  353. }
  354. }
  355. $this->assertSame(
  356. $this->standardizeDates(['2018-04-11', '2018-04-12', '2018-04-13', '2018-04-12', '2018-04-11']),
  357. $this->standardizeDates($results)
  358. );
  359. }
  360. public function testManualIteration()
  361. {
  362. $period = CarbonPeriodFactory::withStackFilter($this->periodClass);
  363. $period->rewind();
  364. $str = '';
  365. while ($period->valid()) {
  366. if ($period->key()) {
  367. $str .= ', ';
  368. }
  369. $str .= $period->current()->format('m-d');
  370. $period->next();
  371. }
  372. $this->assertSame('01-01, 01-03', $str);
  373. }
  374. public function testSkip()
  375. {
  376. $periodClass = $this->periodClass;
  377. $period = $periodClass::create('2018-05-30', '2018-07-13');
  378. $output = [];
  379. foreach ($period as $day) {
  380. /* @var Carbon $day */
  381. $output[] = $day->format('Y-m-d');
  382. if ($day->isSunday()) {
  383. $this->assertTrue($period->skip(7));
  384. $output[] = '...';
  385. }
  386. }
  387. $this->assertSame([
  388. '2018-05-30',
  389. '2018-05-31',
  390. '2018-06-01',
  391. '2018-06-02',
  392. '2018-06-03',
  393. '...',
  394. '2018-06-11',
  395. '2018-06-12',
  396. '2018-06-13',
  397. '2018-06-14',
  398. '2018-06-15',
  399. '2018-06-16',
  400. '2018-06-17',
  401. '...',
  402. '2018-06-25',
  403. '2018-06-26',
  404. '2018-06-27',
  405. '2018-06-28',
  406. '2018-06-29',
  407. '2018-06-30',
  408. '2018-07-01',
  409. '...',
  410. '2018-07-09',
  411. '2018-07-10',
  412. '2018-07-11',
  413. '2018-07-12',
  414. '2018-07-13',
  415. ], $output);
  416. $this->assertFalse($period->skip());
  417. $this->assertFalse($period->skip(7));
  418. }
  419. public function testLocale()
  420. {
  421. /** @var CarbonPeriod $period */
  422. $period = CarbonPeriodFactory::withStackFilter($this->periodClass)->locale('de');
  423. $str = '';
  424. foreach ($period as $key => $date) {
  425. if ($key) {
  426. $str .= ', ';
  427. }
  428. $str .= $date->isoFormat('MMMM dddd');
  429. }
  430. $this->assertSame('Januar Montag, Januar Mittwoch', $str);
  431. }
  432. public function testTimezone()
  433. {
  434. $period = CarbonPeriodFactory::withStackFilter($this->periodClass)->shiftTimezone('America/Toronto');
  435. $str = null;
  436. foreach ($period as $key => $date) {
  437. $str = $date->format('H e');
  438. break;
  439. }
  440. $this->assertSame('00 America/Toronto', $str);
  441. }
  442. }