-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Form][FrameworkBundle][Bridge] Add a DateInterval form type #16809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Extension\Core\DataTransformer; | ||
|
||
use Symfony\Component\Form\DataTransformerInterface; | ||
use Symfony\Component\Form\Exception\TransformationFailedException; | ||
use Symfony\Component\Form\Exception\UnexpectedTypeException; | ||
|
||
/** | ||
* Transforms between a normalized date interval and an interval string/array. | ||
* | ||
* @author Steffen Roßkamp <steffen.rosskamp@gimmickmedia.de> | ||
*/ | ||
class DateIntervalToArrayTransformer implements DataTransformerInterface | ||
{ | ||
const YEARS = 'years'; | ||
const MONTHS = 'months'; | ||
const DAYS = 'days'; | ||
const HOURS = 'hours'; | ||
const MINUTES = 'minutes'; | ||
const SECONDS = 'seconds'; | ||
const INVERT = 'invert'; | ||
|
||
private static $availableFields = array( | ||
self::YEARS => 'y', | ||
self::MONTHS => 'm', | ||
self::DAYS => 'd', | ||
self::HOURS => 'h', | ||
self::MINUTES => 'i', | ||
self::SECONDS => 's', | ||
self::INVERT => 'r', | ||
); | ||
private $fields; | ||
|
||
/** | ||
* @param string[] $fields The date fields | ||
* @param bool $pad Whether to use padding | ||
*/ | ||
public function __construct(array $fields = null, $pad = false) | ||
{ | ||
if (null === $fields) { | ||
$fields = array('years', 'months', 'days', 'hours', 'minutes', 'seconds', 'invert'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would use an associative array here instead of plain array (because you often use |
||
} | ||
$this->fields = $fields; | ||
$this->pad = (bool) $pad; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MisatoTremor We're missing the actual |
||
} | ||
|
||
/** | ||
* Transforms a normalized date interval into an interval array. | ||
* | ||
* @param \DateInterval $dateInterval Normalized date interval. | ||
* | ||
* @return array Interval array. | ||
* | ||
* @throws UnexpectedTypeException If the given value is not a \DateInterval instance. | ||
*/ | ||
public function transform($dateInterval) | ||
{ | ||
if (null === $dateInterval) { | ||
return array_intersect_key( | ||
array( | ||
'years' => '', | ||
'months' => '', | ||
'weeks' => '', | ||
'days' => '', | ||
'hours' => '', | ||
'minutes' => '', | ||
'seconds' => '', | ||
'invert' => false, | ||
), | ||
array_flip($this->fields) | ||
); | ||
} | ||
if (!$dateInterval instanceof \DateInterval) { | ||
throw new UnexpectedTypeException($dateInterval, '\DateInterval'); | ||
} | ||
$result = array(); | ||
foreach (self::$availableFields as $field => $char) { | ||
$result[$field] = $dateInterval->format('%'.($this->pad ? strtoupper($char) : $char)); | ||
} | ||
if (in_array('weeks', $this->fields, true)) { | ||
$result['weeks'] = 0; | ||
if (isset($result['days']) && (int) $result['days'] >= 7) { | ||
$result['weeks'] = (string) floor($result['days'] / 7); | ||
$result['days'] = (string) ($result['days'] % 7); | ||
} | ||
} | ||
$result['invert'] = '-' === $result['invert']; | ||
$result = array_intersect_key($result, array_flip($this->fields)); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Transforms an interval array into a normalized date interval. | ||
* | ||
* @param array $value Interval array | ||
* | ||
* @return \DateInterval Normalized date interval | ||
* | ||
* @throws UnexpectedTypeException If the given value is not an array. | ||
* @throws TransformationFailedException If the value could not be transformed. | ||
*/ | ||
public function reverseTransform($value) | ||
{ | ||
if (null === $value) { | ||
return; | ||
} | ||
if (!is_array($value)) { | ||
throw new UnexpectedTypeException($value, 'array'); | ||
} | ||
if ('' === implode('', $value)) { | ||
return; | ||
} | ||
$emptyFields = array(); | ||
foreach ($this->fields as $field) { | ||
if (!isset($value[$field])) { | ||
$emptyFields[] = $field; | ||
} | ||
} | ||
if (count($emptyFields) > 0) { | ||
throw new TransformationFailedException(sprintf('The fields "%s" should not be empty', implode('", "', $emptyFields))); | ||
} | ||
if (isset($value['invert']) && !is_bool($value['invert'])) { | ||
throw new TransformationFailedException('The value of "invert" must be boolean'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
foreach (self::$availableFields as $field => $char) { | ||
if ($field !== 'invert' && isset($value[$field]) && !ctype_digit((string) $value[$field])) { | ||
throw new TransformationFailedException(sprintf('This amount of "%s" is invalid', $field)); | ||
} | ||
} | ||
try { | ||
if (!empty($value['weeks'])) { | ||
$interval = sprintf( | ||
'P%sY%sM%sWT%sH%sM%sS', | ||
empty($value['years']) ? '0' : $value['years'], | ||
empty($value['months']) ? '0' : $value['months'], | ||
empty($value['weeks']) ? '0' : $value['weeks'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and left over days ? :o There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An ISO date interval can only have either weeks or days. |
||
empty($value['hours']) ? '0' : $value['hours'], | ||
empty($value['minutes']) ? '0' : $value['minutes'], | ||
empty($value['seconds']) ? '0' : $value['seconds'] | ||
); | ||
} else { | ||
$interval = sprintf( | ||
'P%sY%sM%sDT%sH%sM%sS', | ||
empty($value['years']) ? '0' : $value['years'], | ||
empty($value['months']) ? '0' : $value['months'], | ||
empty($value['days']) ? '0' : $value['days'], | ||
empty($value['hours']) ? '0' : $value['hours'], | ||
empty($value['minutes']) ? '0' : $value['minutes'], | ||
empty($value['seconds']) ? '0' : $value['seconds'] | ||
); | ||
} | ||
$dateInterval = new \DateInterval($interval); | ||
if (isset($value['invert'])) { | ||
$dateInterval->invert = $value['invert'] ? 1 : 0; | ||
} | ||
} catch (\Exception $e) { | ||
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); | ||
} | ||
|
||
return $dateInterval; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Extension\Core\DataTransformer; | ||
|
||
use Symfony\Component\Form\DataTransformerInterface; | ||
use Symfony\Component\Form\Exception\TransformationFailedException; | ||
use Symfony\Component\Form\Exception\UnexpectedTypeException; | ||
|
||
/** | ||
* Transforms between a date string and a DateInterval object. | ||
* | ||
* @author Steffen Roßkamp <steffen.rosskamp@gimmickmedia.de> | ||
*/ | ||
class DateIntervalToStringTransformer implements DataTransformerInterface | ||
{ | ||
private $format; | ||
private $parseSigned; | ||
|
||
/** | ||
* Transforms a \DateInterval instance to a string. | ||
* | ||
* @see \DateInterval::format() for supported formats | ||
* | ||
* @param string $format The date format | ||
* @param bool $parseSigned Whether to parse as a signed interval | ||
*/ | ||
public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS', $parseSigned = false) | ||
{ | ||
$this->format = $format; | ||
$this->parseSigned = $parseSigned; | ||
} | ||
|
||
/** | ||
* Transforms a DateInterval object into a date string with the configured format. | ||
* | ||
* @param \DateInterval $value A DateInterval object | ||
* | ||
* @return string An ISO 8601 or relative date string like date interval presentation | ||
* | ||
* @throws UnexpectedTypeException If the given value is not a \DateInterval instance. | ||
*/ | ||
public function transform($value) | ||
{ | ||
if (null === $value) { | ||
return ''; | ||
} | ||
if (!$value instanceof \DateInterval) { | ||
throw new UnexpectedTypeException($value, '\DateInterval'); | ||
} | ||
|
||
return $value->format($this->format); | ||
} | ||
|
||
/** | ||
* Transforms a date string in the configured format into a DateInterval object. | ||
* | ||
* @param string $value An ISO 8601 or date string like date interval presentation | ||
* | ||
* @return \DateInterval An instance of \DateInterval | ||
* | ||
* @throws UnexpectedTypeException If the given value is not a string. | ||
* @throws TransformationFailedException If the date interval could not be parsed. | ||
*/ | ||
public function reverseTransform($value) | ||
{ | ||
if (null === $value) { | ||
return; | ||
} | ||
if (!is_string($value)) { | ||
throw new UnexpectedTypeException($value, 'string'); | ||
} | ||
if ('' === $value) { | ||
return; | ||
} | ||
if (!$this->isISO8601($value)) { | ||
throw new TransformationFailedException('Non ISO 8601 date strings are not supported yet'); | ||
} | ||
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $this->format).'$/'; | ||
if (!preg_match($valuePattern, $value)) { | ||
throw new TransformationFailedException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $value, $this->format)); | ||
} | ||
try { | ||
$dateInterval = new \DateInterval($value); | ||
} catch (\Exception $e) { | ||
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); | ||
} | ||
|
||
return $dateInterval; | ||
} | ||
|
||
private function isISO8601($string) | ||
{ | ||
return preg_match('/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be moved one line up to keep the alphabetical order