-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #288 Add support for relative date-time (DateInterval) (sstok)
This PR was merged into the 2.0-dev branch. Discussion ---------- | Q | A | ------------- | --- | Bug fix? | yes/no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tickets | | License | MIT This adds the `allow_relative` option to `DateTimeType` to allow the usage of datetime relative formatting (date interval). Commits ------- 3aaa930 Add support for relative date-time (DateInterval)
- Loading branch information
Showing
32 changed files
with
1,020 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
allow_relative | ||
~~~~~~~~~~~~~~ | ||
|
||
**type**: ``bool`` **default**: ``false``_ | ||
|
||
Enables the handling of relative Date and/or time formats like | ||
``1 week``, ``6 years 3 hours``. | ||
|
||
The actual datetime is relative to now (current date and time), use the minus (``-``) | ||
sign like ``-1 year`` to invert interval to a past moment. | ||
|
||
Internally this uses `Carbon DateInterval`_ to parse the (localized) format. | ||
|
||
.. caution:: | ||
|
||
For Doctrine DBAL and ORM not all platforms are supported yet. | ||
Currently only PostgreSQL and MySQL/MariaDB are supported. | ||
|
||
Working se | ||
|
||
.. _`Date/Time Format Syntax`: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax | ||
.. _`Carbon DateInterval`: https://carbon.nesbot.com/docs/#api-interval |
128 changes: 128 additions & 0 deletions
128
lib/Core/Extension/Core/DataTransformer/DateIntervalTransformer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the RollerworksSearch package. | ||
* | ||
* (c) Sebastiaan Stok <[email protected]> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace Rollerworks\Component\Search\Extension\Core\DataTransformer; | ||
|
||
use Carbon\CarbonInterval; | ||
use Carbon\Translator; | ||
use Rollerworks\Component\Search\DataTransformer; | ||
use Rollerworks\Component\Search\Exception\TransformationFailedException; | ||
use function Symfony\Component\String\u; | ||
|
||
final class DateIntervalTransformer implements DataTransformer | ||
{ | ||
/** @var string */ | ||
private $fromLocale; | ||
|
||
/** @var string */ | ||
private $toLocale; | ||
|
||
public function __construct(string $fromLocale, string $toLocale = null) | ||
{ | ||
$this->fromLocale = $fromLocale; | ||
$this->toLocale = $toLocale ?? $fromLocale; | ||
} | ||
|
||
/** | ||
* @param CarbonInterval|null $value | ||
*/ | ||
public function transform($value): string | ||
{ | ||
if ($value === null) { | ||
return ''; | ||
} | ||
|
||
if (!$value instanceof CarbonInterval) { | ||
throw new TransformationFailedException('Expected a CarbonInterval instance or null.'); | ||
} | ||
|
||
$value = clone $value; | ||
$value->locale($this->toLocale); | ||
|
||
if ($value->invert === 1) { | ||
return u($value->forHumans())->prepend('-')->toString(); | ||
} | ||
|
||
return $value->forHumans(); | ||
} | ||
|
||
/** | ||
* @param string $value | ||
*/ | ||
public function reverseTransform($value): ?CarbonInterval | ||
{ | ||
if (!is_scalar($value)) { | ||
throw new TransformationFailedException('Expected a scalar.'); | ||
} | ||
|
||
if ($value === '') { | ||
return null; | ||
} | ||
|
||
try { | ||
$value = $this->translateNumberWords($value); | ||
$uValue = u($value)->trim(); | ||
|
||
if ($uValue->startsWith('-')) { | ||
return CarbonInterval::parseFromLocale($uValue->trimStart('-')->toString(), $this->fromLocale)->invert(); | ||
} | ||
|
||
return CarbonInterval::parseFromLocale($uValue->toString(), $this->fromLocale); | ||
} catch (\Exception $e) { | ||
throw new TransformationFailedException('Unable to parse value to DateInterval', 0, $e); | ||
} | ||
} | ||
|
||
private function translateNumberWords(string $timeString): string | ||
{ | ||
$timeString = strtr($timeString, ['’' => "'"]); | ||
|
||
$translator = Translator::get($this->fromLocale); | ||
$translations = $translator->getMessages(); | ||
|
||
if (!isset($translations[$this->fromLocale])) { | ||
return $timeString; | ||
} | ||
|
||
$messages = $translations[$this->fromLocale]; | ||
|
||
foreach (['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] as $item) { | ||
foreach (explode('|', $messages[$item]) as $idx => $messagePart) { | ||
if (preg_match('/[:%](count|time)/', $messagePart)) { | ||
continue; | ||
} | ||
|
||
if ($messagePart[0] === '{') { | ||
$idx = (int) substr($messagePart, 1, strpos($messagePart, '}')); | ||
} | ||
|
||
$messagePart = static::cleanWordFromTranslationString($messagePart); | ||
$timeString = str_replace($messagePart, $idx.' '.$item, $timeString); | ||
} | ||
} | ||
|
||
return $timeString; | ||
} | ||
|
||
/** | ||
* Return the word cleaned from its translation codes. | ||
*/ | ||
private static function cleanWordFromTranslationString(string $word): string | ||
{ | ||
$word = str_replace([':count', '%count', ':time'], '', $word); | ||
$word = strtr($word, ['’' => "'"]); | ||
$word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); | ||
|
||
return trim($word); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
lib/Core/Extension/Core/DataTransformer/MultiTypeDataTransformer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of the RollerworksSearch package. | ||
* | ||
* (c) Sebastiaan Stok <[email protected]> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace Rollerworks\Component\Search\Extension\Core\DataTransformer; | ||
|
||
use Rollerworks\Component\Search\DataTransformer; | ||
use Rollerworks\Component\Search\Exception\TransformationFailedException; | ||
use Throwable; | ||
|
||
/** | ||
* Allows to use multiple transformers based on their type. | ||
* | ||
* This transformer is mostly used by DateTimeType to support | ||
* both DateTime and DateInterval objects. | ||
* | ||
* @internal | ||
*/ | ||
final class MultiTypeDataTransformer implements DataTransformer | ||
{ | ||
/** @var array<class-string,DataTransformer> */ | ||
private $transformers; | ||
|
||
/** | ||
* @param array<class-string,DataTransformer> $transformers | ||
*/ | ||
public function __construct(array $transformers) | ||
{ | ||
$this->transformers = $transformers; | ||
} | ||
|
||
public function transform($value) | ||
{ | ||
if ($value === null) { | ||
return ''; | ||
} | ||
|
||
$type = get_debug_type($value); | ||
|
||
if (!isset($this->transformers[$type])) { | ||
throw new TransformationFailedException(sprintf('Unsupported type "%s".', $type)); | ||
} | ||
|
||
return $this->transformers[$type]->transform($value); | ||
} | ||
|
||
public function reverseTransform($value) | ||
{ | ||
$finalException = null; | ||
|
||
foreach ($this->transformers as $transformer) { | ||
try { | ||
return $transformer->reverseTransform($value); | ||
} catch (Throwable $e) { | ||
$finalException = new TransformationFailedException($e->getMessage().PHP_EOL.$e->getTraceAsString(), $e->getCode(), $finalException); | ||
|
||
continue; | ||
} | ||
} | ||
|
||
throw $finalException; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.