Merge branch '3.4'

* 3.4:
  Implemented PruneableInterface on TagAwareAdapter
  Do not trigger deprecation if node has not been explicitly filled
  Improve microseconds support in date caster
  Use new configuration node depreciation method
This commit is contained in:
Nicolas Grekas 2017-09-04 20:13:11 +02:00
commit 872e77136d
10 changed files with 200 additions and 77 deletions

View File

@ -683,7 +683,9 @@ class Configuration implements ConfigurationInterface
->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->children() ->children()
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end()
->scalarNode('cache')->end() ->scalarNode('cache')
->setDeprecated('The "%path%.%node%" option is deprecated since Symfony 3.1 and will be removed in 4.0. Configure the "cache.serializer" service under "framework.cache.pools" instead.')
->end()
->scalarNode('name_converter')->end() ->scalarNode('name_converter')->end()
->scalarNode('circular_reference_handler')->end() ->scalarNode('circular_reference_handler')->end()
->arrayNode('mapping') ->arrayNode('mapping')

View File

@ -14,11 +14,12 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemInterface;
use Psr\Cache\InvalidArgumentException; use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class TagAwareAdapter implements TagAwareAdapterInterface class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface
{ {
const TAGS_PREFIX = "\0tags\0"; const TAGS_PREFIX = "\0tags\0";
@ -334,4 +335,16 @@ class TagAwareAdapter implements TagAwareAdapterInterface
return $tagVersions; return $tagVersions;
} }
/**
* {@inheritdoc}
*/
public function prune()
{
if ($this->itemsAdapter instanceof PruneableInterface) {
return $this->itemsAdapter->prune();
}
return false;
}
} }

View File

@ -5,7 +5,7 @@ CHANGELOG
----- -----
* added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
* added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, and ChainTrait * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
* now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
ChainCache implement PruneableInterface and support manual stale cache pruning ChainCache implement PruneableInterface and support manual stale cache pruning

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Cache\Tests\Adapter; namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter;
@ -125,4 +126,60 @@ class TagAwareAdapterTest extends AdapterTestCase
$i = $pool->getItem('k'); $i = $pool->getItem('k');
$this->assertSame(array('foo' => 'foo'), $i->getPreviousTags()); $this->assertSame(array('foo' => 'foo'), $i->getPreviousTags());
} }
public function testPrune()
{
$cache = new TagAwareAdapter($this->getPruneableMock());
$this->assertTrue($cache->prune());
$cache = new TagAwareAdapter($this->getNonPruneableMock());
$this->assertFalse($cache->prune());
$cache = new TagAwareAdapter($this->getFailingPruneableMock());
$this->assertFalse($cache->prune());
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface
*/
private function getPruneableMock()
{
$pruneable = $this
->getMockBuilder(PruneableCacheInterface::class)
->getMock();
$pruneable
->expects($this->atLeastOnce())
->method('prune')
->will($this->returnValue(true));
return $pruneable;
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface
*/
private function getFailingPruneableMock()
{
$pruneable = $this
->getMockBuilder(PruneableCacheInterface::class)
->getMock();
$pruneable
->expects($this->atLeastOnce())
->method('prune')
->will($this->returnValue(false));
return $pruneable;
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|AdapterInterface
*/
private function getNonPruneableMock()
{
return $this
->getMockBuilder(AdapterInterface::class)
->getMock();
}
} }

View File

@ -234,10 +234,6 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
} }
foreach ($this->children as $name => $child) { foreach ($this->children as $name => $child) {
if ($child->isDeprecated()) {
@trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED);
}
if (!array_key_exists($name, $value)) { if (!array_key_exists($name, $value)) {
if ($child->isRequired()) { if ($child->isRequired()) {
$msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()); $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath());
@ -254,6 +250,10 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
continue; continue;
} }
if ($child->isDeprecated()) {
@trigger_error($child->getDeprecationMessage($name, $this->getPath()), E_USER_DEPRECATED);
}
try { try {
$value[$name] = $child->finalize($value[$name]); $value[$name] = $child->finalize($value[$name]);
} catch (UnsetKeyException $e) { } catch (UnsetKeyException $e) {

View File

@ -84,10 +84,6 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface
*/ */
protected function finalizeValue($value) protected function finalizeValue($value)
{ {
if ($this->deprecationMessage) {
@trigger_error($this->getDeprecationMessage($this->getName(), $this->getPath()), E_USER_DEPRECATED);
}
if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
$ex = new InvalidConfigurationException(sprintf( $ex = new InvalidConfigurationException(sprintf(
'The path "%s" cannot contain an empty value, but got %s.', 'The path "%s" cannot contain an empty value, but got %s.',

View File

@ -219,10 +219,33 @@ class ArrayNodeTest extends TestCase
public function testSetDeprecated() public function testSetDeprecated()
{ {
$node = new ArrayNode('foo'); $childNode = new ArrayNode('foo');
$node->setDeprecated('"%node%" is deprecated'); $childNode->setDeprecated('"%node%" is deprecated');
$this->assertTrue($node->isDeprecated()); $this->assertTrue($childNode->isDeprecated());
$this->assertSame('"foo" is deprecated', $node->getDeprecationMessage($node->getName(), $node->getPath())); $this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath()));
$node = new ArrayNode('root');
$node->addChild($childNode);
$deprecationTriggered = false;
$deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) {
if (E_USER_DEPRECATED === $level) {
return $deprecationTriggered = true;
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
};
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array());
restore_error_handler();
$this->assertFalse($deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set');
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array('foo' => array()));
restore_error_handler();
$this->assertTrue($deprecationTriggered, '->finalize() should trigger if the deprecated node is set');
} }
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Config\Tests\Definition; namespace Symfony\Component\Config\Tests\Definition;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\ScalarNode; use Symfony\Component\Config\Definition\ScalarNode;
class ScalarNodeTest extends TestCase class ScalarNodeTest extends TestCase
@ -42,11 +43,33 @@ class ScalarNodeTest extends TestCase
public function testSetDeprecated() public function testSetDeprecated()
{ {
$node = new ScalarNode('foo'); $childNode = new ScalarNode('foo');
$node->setDeprecated('"%node%" is deprecated'); $childNode->setDeprecated('"%node%" is deprecated');
$this->assertTrue($node->isDeprecated()); $this->assertTrue($childNode->isDeprecated());
$this->assertSame('"foo" is deprecated', $node->getDeprecationMessage($node->getName(), $node->getPath())); $this->assertSame('"foo" is deprecated', $childNode->getDeprecationMessage($childNode->getName(), $childNode->getPath()));
$node = new ArrayNode('root');
$node->addChild($childNode);
$deprecationTriggered = 0;
$deprecationHandler = function ($level, $message, $file, $line) use (&$prevErrorHandler, &$deprecationTriggered) {
if (E_USER_DEPRECATED === $level) {
return ++$deprecationTriggered;
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
};
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array());
restore_error_handler();
$this->assertSame(0, $deprecationTriggered, '->finalize() should not trigger if the deprecated node is not set');
$prevErrorHandler = set_error_handler($deprecationHandler);
$node->finalize(array('foo' => ''));
restore_error_handler();
$this->assertSame(1, $deprecationTriggered, '->finalize() should trigger if the deprecated node is set');
} }
/** /**

View File

@ -34,7 +34,7 @@ class DateCaster
; ;
$a = array(); $a = array();
$a[$prefix.'date'] = new ConstStub($d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).($location ? ' e (P)' : ' P')), $title); $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title);
$stub->class .= $d->format(' @U'); $stub->class .= $d->format(' @U');
@ -63,7 +63,7 @@ class DateCaster
$format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : '');
} }
$format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, $i->f) : ''; $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : '';
$format = '%R ' === $format ? '0s' : $format; $format = '%R ' === $format ? '0s' : $format;
return $i->format(rtrim($format)); return $i->format(rtrim($format));
@ -93,16 +93,16 @@ class DateCaster
); );
break; break;
} }
$dates[] = sprintf('%s) %s', $i + 1, $d->format('Y-m-d H:i:s')); $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d));
} }
} }
$period = sprintf( $period = sprintf(
'every %s, from %s (%s) %s', 'every %s, from %s (%s) %s',
self::formatInterval($p->getDateInterval()), self::formatInterval($p->getDateInterval()),
$p->getStartDate()->format('Y-m-d H:i:s'), self::formatDateTime($p->getStartDate()),
$p->include_start_date ? 'included' : 'excluded', $p->include_start_date ? 'included' : 'excluded',
($end = $p->getEndDate()) ? 'to '.$end->format('Y-m-d H:i:s') : 'recurring '.$p->recurrences.' time/s' ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s'
); );
$p = array(Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))); $p = array(Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates)));
@ -110,6 +110,11 @@ class DateCaster
return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a;
} }
private static function formatDateTime(\DateTimeInterface $d, $extra = '')
{
return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra);
}
private static function formatSeconds($s, $us) private static function formatSeconds($s, $us)
{ {
return sprintf('%02d.%s', $s, 0 === ($len = strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); return sprintf('%02d.%s', $s, 0 === ($len = strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us));

View File

@ -92,10 +92,9 @@ EODUMP;
/** /**
* @dataProvider provideIntervals * @dataProvider provideIntervals
*/ */
public function testDumpInterval($intervalSpec, $invert, $expected) public function testDumpInterval($intervalSpec, $ms, $invert, $expected)
{ {
$interval = new \DateInterval($intervalSpec); $interval = $this->createInterval($intervalSpec, $ms, $invert);
$interval->invert = $invert;
$xDump = <<<EODUMP $xDump = <<<EODUMP
DateInterval { DateInterval {
@ -109,10 +108,9 @@ EODUMP;
/** /**
* @dataProvider provideIntervals * @dataProvider provideIntervals
*/ */
public function testDumpIntervalExcludingVerbosity($intervalSpec, $invert, $expected) public function testDumpIntervalExcludingVerbosity($intervalSpec, $ms, $invert, $expected)
{ {
$interval = new \DateInterval($intervalSpec); $interval = $this->createInterval($intervalSpec, $ms, $invert);
$interval->invert = $invert;
$xDump = <<<EODUMP $xDump = <<<EODUMP
DateInterval { DateInterval {
@ -126,10 +124,9 @@ EODUMP;
/** /**
* @dataProvider provideIntervals * @dataProvider provideIntervals
*/ */
public function testCastInterval($intervalSpec, $invert, $xInterval, $xSeconds) public function testCastInterval($intervalSpec, $ms, $invert, $xInterval, $xSeconds)
{ {
$interval = new \DateInterval($intervalSpec); $interval = $this->createInterval($intervalSpec, $ms, $invert);
$interval->invert = $invert;
$stub = new Stub(); $stub = new Stub();
$cast = DateCaster::castInterval($interval, array('foo' => 'bar'), $stub, false, Caster::EXCLUDE_VERBOSE); $cast = DateCaster::castInterval($interval, array('foo' => 'bar'), $stub, false, Caster::EXCLUDE_VERBOSE);
@ -159,37 +156,39 @@ Symfony\Component\VarDumper\Caster\ConstStub {
} }
EODUMP; EODUMP;
$this->assertDumpEquals($xDump, $cast["\0~\0interval"]); $this->assertDumpMatchesFormat($xDump, $cast["\0~\0interval"]);
} }
public function provideIntervals() public function provideIntervals()
{ {
return array( return array(
array('PT0S', 0, '0s', '0s'), array('PT0S', 0, 0, '0s', '0s'),
array('PT1S', 0, '+ 00:00:01.0', '1s'), array('PT0S', 0.1, 0, '+ 00:00:00.100', '%is'),
array('PT2M', 0, '+ 00:02:00.0', '120s'), array('PT1S', 0, 0, '+ 00:00:01.0', '1s'),
array('PT3H', 0, '+ 03:00:00.0', '10 800s'), array('PT2M', 0, 0, '+ 00:02:00.0', '120s'),
array('P4D', 0, '+ 4d', '345 600s'), array('PT3H', 0, 0, '+ 03:00:00.0', '10 800s'),
array('P5M', 0, '+ 5m', null), array('P4D', 0, 0, '+ 4d', '345 600s'),
array('P6Y', 0, '+ 6y', null), array('P5M', 0, 0, '+ 5m', null),
array('P1Y2M3DT4H5M6S', 0, '+ 1y 2m 3d 04:05:06.0', null), array('P6Y', 0, 0, '+ 6y', null),
array('PT1M60S', 0, '+ 00:02:00.0', null), array('P1Y2M3DT4H5M6S', 0, 0, '+ 1y 2m 3d 04:05:06.0', null),
array('PT1H60M', 0, '+ 02:00:00.0', null), array('PT1M60S', 0, 0, '+ 00:02:00.0', null),
array('P1DT24H', 0, '+ 2d', null), array('PT1H60M', 0, 0, '+ 02:00:00.0', null),
array('P1M32D', 0, '+ 1m 32d', null), array('P1DT24H', 0, 0, '+ 2d', null),
array('P1M32D', 0, 0, '+ 1m 32d', null),
array('PT0S', 1, '0s', '0s'), array('PT0S', 0, 1, '0s', '0s'),
array('PT1S', 1, '- 00:00:01.0', '-1s'), array('PT0S', 0.1, 1, '- 00:00:00.100', '%is'),
array('PT2M', 1, '- 00:02:00.0', '-120s'), array('PT1S', 0, 1, '- 00:00:01.0', '-1s'),
array('PT3H', 1, '- 03:00:00.0', '-10 800s'), array('PT2M', 0, 1, '- 00:02:00.0', '-120s'),
array('P4D', 1, '- 4d', '-345 600s'), array('PT3H', 0, 1, '- 03:00:00.0', '-10 800s'),
array('P5M', 1, '- 5m', null), array('P4D', 0, 1, '- 4d', '-345 600s'),
array('P6Y', 1, '- 6y', null), array('P5M', 0, 1, '- 5m', null),
array('P1Y2M3DT4H5M6S', 1, '- 1y 2m 3d 04:05:06.0', null), array('P6Y', 0, 1, '- 6y', null),
array('PT1M60S', 1, '- 00:02:00.0', null), array('P1Y2M3DT4H5M6S', 0, 1, '- 1y 2m 3d 04:05:06.0', null),
array('PT1H60M', 1, '- 02:00:00.0', null), array('PT1M60S', 0, 1, '- 00:02:00.0', null),
array('P1DT24H', 1, '- 2d', null), array('PT1H60M', 0, 1, '- 02:00:00.0', null),
array('P1M32D', 1, '- 1m 32d', null), array('P1DT24H', 0, 1, '- 2d', null),
array('P1M32D', 0, 1, '- 1m 32d', null),
); );
} }
@ -318,7 +317,7 @@ array:1 [
] ]
EODUMP; EODUMP;
$this->assertDumpMatchesFormat($xDump, $cast); $this->assertDumpEquals($xDump, $cast);
$xDump = <<<EODUMP $xDump = <<<EODUMP
Symfony\Component\VarDumper\Caster\ConstStub { Symfony\Component\VarDumper\Caster\ConstStub {
@ -339,32 +338,37 @@ EODUMP;
public function providePeriods() public function providePeriods()
{ {
$periods = array( $periods = array(
array('2017-01-01', 'P1D', '2017-01-03', 0, 'every + 1d, from 2017-01-01 00:00:00 (included) to 2017-01-03 00:00:00', '1) 2017-01-01%a2) 2017-01-02'), array('2017-01-01', 'P1D', '2017-01-03', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-03 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02'),
array('2017-01-01', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 00:00:00 (included) recurring 2 time/s', '1) 2017-01-01%a2) 2017-01-02'), array('2017-01-01', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 2 time/s', '1) 2017-01-01%a2) 2017-01-02'),
array('2017-01-01', 'P1D', '2017-01-04', 0, 'every + 1d, from 2017-01-01 00:00:00 (included) to 2017-01-04 00:00:00', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'), array('2017-01-01', 'P1D', '2017-01-04', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-04 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'),
array('2017-01-01', 'P1D', 2, 0, 'every + 1d, from 2017-01-01 00:00:00 (included) recurring 3 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'), array('2017-01-01', 'P1D', 2, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 3 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03'),
array('2017-01-01', 'P1D', '2017-01-05', 0, 'every + 1d, from 2017-01-01 00:00:00 (included) to 2017-01-05 00:00:00', '1) 2017-01-01%a2) 2017-01-02%a1 more'), array('2017-01-01', 'P1D', '2017-01-05', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-05 00:00:00.0', '1) 2017-01-01%a2) 2017-01-02%a1 more'),
array('2017-01-01', 'P1D', 3, 0, 'every + 1d, from 2017-01-01 00:00:00 (included) recurring 4 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03%a1 more'), array('2017-01-01', 'P1D', 3, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 4 time/s', '1) 2017-01-01%a2) 2017-01-02%a3) 2017-01-03%a1 more'),
array('2017-01-01', 'P1D', '2017-01-21', 0, 'every + 1d, from 2017-01-01 00:00:00 (included) to 2017-01-21 00:00:00', '1) 2017-01-01%a17 more'), array('2017-01-01', 'P1D', '2017-01-21', 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) to 2017-01-21 00:00:00.0', '1) 2017-01-01%a17 more'),
array('2017-01-01', 'P1D', 19, 0, 'every + 1d, from 2017-01-01 00:00:00 (included) recurring 20 time/s', '1) 2017-01-01%a17 more'), array('2017-01-01', 'P1D', 19, 0, 'every + 1d, from 2017-01-01 00:00:00.0 (included) recurring 20 time/s', '1) 2017-01-01%a17 more'),
array('2017-01-01 01:00:00', 'P1D', '2017-01-03 01:00:00', 0, 'every + 1d, from 2017-01-01 01:00:00 (included) to 2017-01-03 01:00:00', '1) 2017-01-01 01:00:00%a2) 2017-01-02 01:00:00'), array('2017-01-01 01:00:00', 'P1D', '2017-01-03 01:00:00', 0, 'every + 1d, from 2017-01-01 01:00:00.0 (included) to 2017-01-03 01:00:00.0', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'),
array('2017-01-01 01:00:00', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 01:00:00 (included) recurring 2 time/s', '1) 2017-01-01 01:00:00%a2) 2017-01-02 01:00:00'), array('2017-01-01 01:00:00', 'P1D', 1, 0, 'every + 1d, from 2017-01-01 01:00:00.0 (included) recurring 2 time/s', '1) 2017-01-01 01:00:00.0%a2) 2017-01-02 01:00:00.0'),
array('2017-01-01', 'P1DT1H', '2017-01-03', 0, 'every + 1d 01:00:00.0, from 2017-01-01 00:00:00 (included) to 2017-01-03 00:00:00', '1) 2017-01-01 00:00:00%a2) 2017-01-02 01:00:00'), array('2017-01-01', 'P1DT1H', '2017-01-03', 0, "every + 1d 01:00:00.0, from 2017-01-01 00:00:00.0 (included) to 2017-01-03 00:00:00.0", '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'),
array('2017-01-01', 'P1DT1H', 1, 0, 'every + 1d 01:00:00.0, from 2017-01-01 00:00:00 (included) recurring 2 time/s', '1) 2017-01-01 00:00:00%a2) 2017-01-02 01:00:00'), array('2017-01-01', 'P1DT1H', 1, 0, "every + 1d 01:00:00.0, from 2017-01-01 00:00:00.0 (included) recurring 2 time/s", '1) 2017-01-01 00:00:00.0%a2) 2017-01-02 01:00:00.0'),
array('2017-01-01', 'P1D', '2017-01-04', \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00 (excluded) to 2017-01-04 00:00:00', '1) 2017-01-02%a2) 2017-01-03'), array('2017-01-01', 'P1D', '2017-01-04', \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00.0 (excluded) to 2017-01-04 00:00:00.0', '1) 2017-01-02%a2) 2017-01-03'),
array('2017-01-01', 'P1D', 2, \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00 (excluded) recurring 2 time/s', '1) 2017-01-02%a2) 2017-01-03'), array('2017-01-01', 'P1D', 2, \DatePeriod::EXCLUDE_START_DATE, 'every + 1d, from 2017-01-01 00:00:00.0 (excluded) recurring 2 time/s', '1) 2017-01-02%a2) 2017-01-03'),
); );
if (\PHP_VERSION_ID < 70107) {
array_walk($periods, function (&$i) { $i[5] = ''; });
}
return $periods; return $periods;
} }
private function createInterval($intervalSpec, $ms, $invert)
{
$interval = new \DateInterval($intervalSpec);
$interval->f = $ms;
$interval->invert = $invert;
return $interval;
}
} }