This PR was merged into the 2.7 branch.
Discussion
----------
Fix #19531 [Form] DateType fails parsing when midnight is not a valid time
| Q | A
| ------------- | ---
| Branch? | 2.7
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #19531
| License | MIT
| Doc PR |
Commits
-------
c951bb6
Fix #19531 [Form] DateType fails parsing when midnight is not a valid time
This commit is contained in:
commit
b405df0925
@ -117,20 +117,29 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
|
|||||||
return;
|
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) {
|
if (intl_get_error_code() != 0) {
|
||||||
throw new TransformationFailedException(intl_get_error_message());
|
throw new TransformationFailedException(intl_get_error_message());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// read timestamp into DateTime object - the formatter delivers in UTC
|
if ($dateOnly) {
|
||||||
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
|
// 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) {
|
} catch (\Exception $e) {
|
||||||
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
|
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('UTC' !== $this->inputTimezone) {
|
if ($this->outputTimezone !== $this->inputTimezone) {
|
||||||
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
|
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,15 +149,17 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
|
|||||||
/**
|
/**
|
||||||
* Returns a preconfigured IntlDateFormatter instance.
|
* Returns a preconfigured IntlDateFormatter instance.
|
||||||
*
|
*
|
||||||
|
* @param bool $ignoreTimezone Use UTC regardless of the configured timezone.
|
||||||
|
*
|
||||||
* @return \IntlDateFormatter
|
* @return \IntlDateFormatter
|
||||||
*
|
*
|
||||||
* @throws TransformationFailedException in case the date formatter can not be constructed.
|
* @throws TransformationFailedException in case the date formatter can not be constructed.
|
||||||
*/
|
*/
|
||||||
protected function getIntlDateFormatter()
|
protected function getIntlDateFormatter($ignoreTimezone = false)
|
||||||
{
|
{
|
||||||
$dateFormat = $this->dateFormat;
|
$dateFormat = $this->dateFormat;
|
||||||
$timeFormat = $this->timeFormat;
|
$timeFormat = $this->timeFormat;
|
||||||
$timezone = $this->outputTimezone;
|
$timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone;
|
||||||
$calendar = $this->calendar;
|
$calendar = $this->calendar;
|
||||||
$pattern = $this->pattern;
|
$pattern = $this->pattern;
|
||||||
|
|
||||||
@ -163,4 +174,24 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
|
|||||||
|
|
||||||
return $intlDateFormatter;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,26 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
|
|||||||
$this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06'));
|
$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()
|
public function testReverseTransformEmpty()
|
||||||
{
|
{
|
||||||
$transformer = new DateTimeToLocalizedStringTransformer();
|
$transformer = new DateTimeToLocalizedStringTransformer();
|
||||||
|
Reference in New Issue
Block a user