diff --git a/docs/book/standard-filters.md b/docs/book/standard-filters.md index 32b95633..a711c99d 100644 --- a/docs/book/standard-filters.md +++ b/docs/book/standard-filters.md @@ -713,6 +713,30 @@ All options can be set at instantiation or by using a related method. For exampl methods for `target` are `getTarget()` and `setTarget()`. You can also use the `setOptions()` method which accepts an array of all options. +## DateTime + +Converts `$value` to a `DateTime` or `DateTimeImmutable` instance. + +### Supported Options + +The following options are supported for `Zend\Filter\DateTime`: + +- `immutable`: `true` if a `DateTimeImmutable` instance should be returned, + `false` (the default) if `DateTime` is expected. Methods for getting/setting + this option are available: `isImmutable()` and `setImmutable(bool)`. + +### Basic usage + +```php +$filter = new Zend\Filter\DateTime(); +$dt = $filter->filter('2019-04-24T14:33:12+02:00'); +// $dt is a DateTime instance with the above date, time and timezone + +$filter = new Zend\Filter\DateTime(true); +$dt = $filter->filter('2019-04-24T14:34:34+02:00'); +// $dt is a DateTimeImmutable instance with the above date, time and timezone +``` + ## Digits Returns the string `$value`, removing all but digits. diff --git a/src/DateTime.php b/src/DateTime.php new file mode 100644 index 00000000..956356ba --- /dev/null +++ b/src/DateTime.php @@ -0,0 +1,108 @@ + false, + ]; + + /** @param array|bool|null|Traversable $immutableOrOptions */ + public function __construct($immutableOrOptions = null) + { + if ($immutableOrOptions !== null) { + if ($immutableOrOptions instanceof Traversable) { + $immutableOrOptions = iterator_to_array($immutableOrOptions); + } + + if (is_array($immutableOrOptions)) { + if (isset($immutableOrOptions['immutable'])) { + $this->setOptions($immutableOrOptions); + } else { + $this->setImmutable($immutableOrOptions); + } + } else { + $this->setImmutable($immutableOrOptions); + } + } + } + + /** + * Defined by Zend\Filter\FilterInterface + * + * Returns (DateTimeInterface) $value + * + * If the value provided is neither a string nor instance of + * DateTimeInterface, it will remain unfiltered + * + * @param mixed $value + * @return mixed + */ + public function filter($value) + { + $immutable = $this->isImmutable(); + + $isDateTime = $value instanceof PhpDateTime; + $isDateTimeImmutable = $value instanceof DateTimeImmutable; + + if (($isDateTimeImmutable && $immutable) + || ($isDateTime && ! $immutable) + || ! ($isDateTime || $isDateTimeImmutable || is_string($value))) { + // no conversion + return $value; + } + + if ($isDateTimeImmutable && ! $immutable) { + // create mutable DateTime from DateTimeImmutable + $mutableDateTime = new PhpDateTime('@0'); + $mutableDateTime = $mutableDateTime->sub($value->diff($mutableDateTime)); + $mutableDateTime->setTimezone($value->getTimezone()); + return $mutableDateTime; + } + + if ($isDateTime && $immutable) { + // create DateTimeImmutable from mutable DateTime + return DateTimeImmutable::createFromMutable($value); + } + + if ($immutable) { + return new DateTimeImmutable($value); + } + + return new PhpDateTime($value); + } + + /** @return bool */ + public function isImmutable() + { + return $this->options['immutable']; + } + + /** + * @param bool $immutable + * @return self + * @throws Exception\InvalidArgumentException + */ + public function setImmutable($immutable) + { + if (! is_bool($immutable)) { + throw new Exception\InvalidArgumentException('Expected $immutable to be a boolean'); + } + + $this->options['immutable'] = $immutable; + return $this; + } +} diff --git a/test/DateTimeTest.php b/test/DateTimeTest.php new file mode 100644 index 00000000..f0d59e88 --- /dev/null +++ b/test/DateTimeTest.php @@ -0,0 +1,103 @@ +assertEquals(false, $filter->isImmutable()); + } + + public function testConstructorOptions() + { + $filter = new DateTimeFilter(['immutable' => true]); + + $this->assertEquals(true, $filter->isImmutable()); + } + + public function testConstructorParams() + { + $filter = new DateTimeFilter(true); + + $this->assertEquals(true, $filter->isImmutable()); + } + + /** + * @param bool $immutable + * @param mixed $value + * @dataProvider noConversionProvider + */ + public function testNoConversion($immutable, $value) + { + $filter = new DateTimeFilter($immutable); + $this->assertSame($value, $filter->filter($value)); + } + + public function testRealConversion() + { + $dateTime = '2019-04-24T14:25:55.123+02:00'; + $filter = new DateTimeFilter(true); + + $filtered = $filter->filter($dateTime); + + $this->assertInstanceOf(DateTimeImmutable::class, $filtered); + $this->assertEquals(new DateTimeImmutable($dateTime), $filtered); + + $dateTime = '2019-04-24T14:26:22.456+02:00'; + $filter = new DateTimeFilter(); + + $filtered = $filter->filter($dateTime); + + $this->assertInstanceOf(DateTime::class, $filtered); + $this->assertEquals(new DateTime($dateTime), $filter->filter($dateTime)); + } + + public function testSimpleConversion() + { + $dateTime = new DateTime(); + $filter = new DateTimeFilter(true); + + $filtered = $filter->filter($dateTime); + + $this->assertInstanceOf(DateTimeImmutable::class, $filtered); + $this->assertEquals($dateTime, $filtered); + + $dateTime = new DateTimeImmutable(); + $filter = new DateTimeFilter(); + + $filtered = $filter->filter($dateTime); + + $this->assertInstanceOf(DateTime::class, $filtered); + $this->assertEquals($dateTime, $filter->filter($dateTime)); + } + + public function noConversionProvider() + { + return [ + [false, new DateTime()], + [false, null], + [false, 42], + [false, 3.14], + [true, new DateTimeImmutable()], + [true, null], + [true, 42], + [true, 3.14], + ]; + } +}