Fix #19531 [Form] DateType fails parsing when midnight is not a valid time

This commit is contained in:
Matteo Beccati 2016-08-05 12:25:39 +02:00
parent aae8e3da57
commit c951bb6e97
2 changed files with 57 additions and 6 deletions

View File

@ -117,20 +117,29 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return;
}
$timestamp = $this->getIntlDateFormatter()->parse($value);
// date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due
// to DST changes
$dateOnly = $this->isPatternDateOnly();
$timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value);
if (intl_get_error_code() != 0) {
throw new TransformationFailedException(intl_get_error_message());
}
try {
// read timestamp into DateTime object - the formatter delivers in UTC
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
if ($dateOnly) {
// we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
}
// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone));
} catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
if ('UTC' !== $this->inputTimezone) {
if ($this->outputTimezone !== $this->inputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
}
@ -140,15 +149,17 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
/**
* Returns a preconfigured IntlDateFormatter instance.
*
* @param bool $ignoreTimezone Use UTC regardless of the configured timezone.
*
* @return \IntlDateFormatter
*
* @throws TransformationFailedException in case the date formatter can not be constructed.
*/
protected function getIntlDateFormatter()
protected function getIntlDateFormatter($ignoreTimezone = false)
{
$dateFormat = $this->dateFormat;
$timeFormat = $this->timeFormat;
$timezone = $this->outputTimezone;
$timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone;
$calendar = $this->calendar;
$pattern = $this->pattern;
@ -163,4 +174,24 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return $intlDateFormatter;
}
/**
* Checks if the pattern contains only a date.
*
* @param string $pattern The input pattern
*
* @return bool
*/
protected function isPatternDateOnly()
{
if (null === $this->pattern) {
return false;
}
// strip escaped text
$pattern = preg_replace("#'(.*?)'#", '', $this->pattern);
// check for the absence of time-related placeholders
return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern);
}
}

View File

@ -230,6 +230,26 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
$this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06'));
}
public function testReverseTransformDateOnlyWithDstIssue()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy');
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('28/05/1978')
);
}
public function testReverseTransformDateOnlyWithDstIssueAndEscapedText()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy");
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('day: 28 month: 05 year: 1978')
);
}
public function testReverseTransformEmpty()
{
$transformer = new DateTimeToLocalizedStringTransformer();