diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c679d2..b719312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,10 @@ All notable changes to this project will be documented in this file, in reverse ### Changed -- Nothing. +- [#69](https://github.com/zendframework/zend-hydrator/pull/69) adds support for special pre/post characters in formats passed to the + `DateTimeFormatterStrategy`. When used, the `DateTime` instances created + during hydration will (generally) omit the time element, allowing for more + accurate comparisons. ### Deprecated diff --git a/docs/book/strategy.md b/docs/book/strategy.md index bf70675..6e06b8f 100644 --- a/docs/book/strategy.md +++ b/docs/book/strategy.md @@ -104,6 +104,12 @@ This is a strategy that allows you to pass in options for: and DateTime instances. The input and output formats can be provided as constructor arguments. +As of version 2.4.1, this strategy now allows `DateTime` formats that use `!` to +prepend the format, or `|` or `+` to append it; these ensure that, during +hydration, the new `DateTime` instance created will set the time element +accordingly. As a specific example, `Y-m-d|` will drop the time component, +ensuring comparisons are based on a midnight time value. + ### Zend\\Hydrator\\Strategy\\DefaultStrategy The `DefaultStrategy` simply proxies everything through, without performing any diff --git a/src/Strategy/DateTimeFormatterStrategy.php b/src/Strategy/DateTimeFormatterStrategy.php index c106993..24955af 100644 --- a/src/Strategy/DateTimeFormatterStrategy.php +++ b/src/Strategy/DateTimeFormatterStrategy.php @@ -16,6 +16,8 @@ final class DateTimeFormatterStrategy implements StrategyInterface { /** + * Format to use during hydration. + * * @var string */ private $format; @@ -25,6 +27,18 @@ final class DateTimeFormatterStrategy implements StrategyInterface */ private $timezone; + /** + * Format to use during extraction. + * + * Removes any special anchor characters used to ensure that creation of a + * `DateTime` instance uses the formatted time string (which is useful + * during hydration). These include `!` at the beginning of the string and + * `|` at the end. + * + * @var string + */ + private $extractionFormat; + /** * Constructor * @@ -33,8 +47,9 @@ final class DateTimeFormatterStrategy implements StrategyInterface */ public function __construct($format = DateTime::RFC3339, DateTimeZone $timezone = null) { - $this->format = (string) $format; + $this->format = (string) $format; $this->timezone = $timezone; + $this->extractionFormat = preg_replace('/(?format); } /** @@ -42,14 +57,13 @@ public function __construct($format = DateTime::RFC3339, DateTimeZone $timezone * * Converts to date time string * - * @param mixed|DateTime $value - * + * @param mixed|DateTimeInterface $value * @return mixed|string */ public function extract($value) { if ($value instanceof DateTimeInterface) { - return $value->format($this->format); + return $value->format($this->extractionFormat); } return $value; @@ -61,7 +75,6 @@ public function extract($value) * {@inheritDoc} * * @param mixed|string $value - * * @return mixed|DateTime */ public function hydrate($value) @@ -70,11 +83,9 @@ public function hydrate($value) return; } - if ($this->timezone) { - $hydrated = DateTime::createFromFormat($this->format, $value, $this->timezone); - } else { - $hydrated = DateTime::createFromFormat($this->format, $value); - } + $hydrated = $this->timezone + ? DateTime::createFromFormat($this->format, $value, $this->timezone) + : DateTime::createFromFormat($this->format, $value); return $hydrated ?: $value; } diff --git a/test/Strategy/DateTimeFormatterStrategyTest.php b/test/Strategy/DateTimeFormatterStrategyTest.php index 2fadc8c..e0e8d7e 100644 --- a/test/Strategy/DateTimeFormatterStrategyTest.php +++ b/test/Strategy/DateTimeFormatterStrategyTest.php @@ -22,6 +22,7 @@ */ class DateTimeFormatterStrategyTest extends TestCase { + public function testHydrate() { $strategy = new DateTimeFormatterStrategy('Y-m-d'); @@ -100,4 +101,49 @@ public function testCanExtractAnyDateTimeInterface() $strategy->extract($dateMock); $strategy->extract($dateImmutableMock); } + + /** + * @dataProvider formatsWithSpecialCharactersProvider + * @param string $format + * @param string $expectedValue + */ + public function testAcceptsCreateFromFormatSpecialCharacters($format, $expectedValue) + { + $strategy = new DateTimeFormatterStrategy($format); + $hydrated = $strategy->hydrate($expectedValue); + + $this->assertInstanceOf(DateTime::class, $hydrated); + $this->assertEquals($expectedValue, $hydrated->format('Y-m-d')); + } + + /** + * @dataProvider formatsWithSpecialCharactersProvider + * @param string $format + * @param string $expectedValue + */ + public function testCanExtractWithCreateFromFormatSpecialCharacters($format, $expectedValue) + { + $date = DateTime::createFromFormat($format, $expectedValue); + $strategy = new DateTimeFormatterStrategy($format); + $extracted = $strategy->extract($date); + + $this->assertEquals($expectedValue, $extracted); + } + + public function testCanExtractWithCreateFromFormatEscapedSpecialCharacters() + { + $date = DateTime::createFromFormat('Y-m-d', '2018-02-05'); + $strategy = new DateTimeFormatterStrategy('Y-m-d\\+'); + $extracted = $strategy->extract($date); + $this->assertEquals('2018-02-05+', $extracted); + } + + public function formatsWithSpecialCharactersProvider() + { + return [ + '!-prepended' => ['!Y-m-d', '2018-02-05'], + '|-appended' => ['Y-m-d|', '2018-02-05'], + '+-appended' => ['Y-m-d+', '2018-02-05'], + ]; + } }