From 5d3d1b25e076567420248ee69b2144b8ad4ed2ff Mon Sep 17 00:00:00 2001 From: Jakub Zalas Date: Wed, 3 May 2017 18:59:05 +0100 Subject: [PATCH] [Intl][Form] Update tests, TimeZoneTransformer, and DateTimeToLocalizedStringTransformer for the GMT and UTC split in ICU The [GMT timezone has been split from the UTC](http://site.icu-project.org/download/59) timezone [in CLDR](http://cldr.unicode.org/index/downloads/cldr-31) (which ICU is based on). For example, the code blow: * before ICU 59.1 would return "GMT" in all cases * with ICU 59.1 it returns "UTC" for the first three ('z', 'zz', 'zzz') and "Coordinated Universal Time" for the last two ('zzzz', 'zzzzz'). ```php foreach (['z', 'zz', 'zzz', 'zzzz', 'zzzzz'] as $pattern) { $formatter = new \IntlDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('UTC'), IntlDateFormatter::GREGORIAN, $pattern); var_dump($formatter->format(new \DateTime('@0'))); } ``` Similarly Form's `DateTimeToLocalizedStringTransformer` is also affected: ```php $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::FULL); var_dump($transformer->transform(new \DateTime('2010-02-03 04:05:06 UTC'))); // ICU 58.2: '03.02.2010, 04:05:06 GMT' // ICU 59.1: '03.02.2010, 04:05:06 Koordinierte Weltzeit' ``` Refer to added and modified test cases for more changes. I split this PR in two commits for easier review. First commit updates ICU data (generated files), the second updates code and test cases to be compatible with updated data. --- ...teTimeToLocalizedStringTransformerTest.php | 14 ++- .../DateFormat/TimeZoneTransformer.php | 25 ++++- src/Symfony/Component/Intl/README.md | 2 + .../AbstractIntlDateFormatterTest.php | 106 +++++++++++++++--- .../Verification/IntlDateFormatterTest.php | 28 +++++ 5 files changed, 155 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index 7d262fe644..1562071edf 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -61,11 +61,13 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase array(\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 3. Februar 2010', '2010-02-03 00:00:00 UTC'), array(null, \IntlDateFormatter::SHORT, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'), array(null, \IntlDateFormatter::MEDIUM, null, '03.02.2010, 04:05:06', '2010-02-03 04:05:06 UTC'), - array(null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 GMT', '2010-02-03 04:05:06 UTC'), + array(null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 UTC'), + array(null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 GMT'), // see below for extra test case for time format FULL array(\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'), array(\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'), - array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 GMT', '1970-01-01 04:05:06 UTC'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 GMT'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 UTC'), array(null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'), array(null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'), array(null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'), @@ -85,6 +87,9 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase */ public function testTransform($dateFormat, $timeFormat, $pattern, $output, $input) { + IntlTestHelper::requireFullIntl($this, '59.1'); + \Locale::setDefault('de_AT'); + $transformer = new DateTimeToLocalizedStringTransformer( 'UTC', 'UTC', @@ -101,9 +106,12 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase public function testTransformFullTime() { + IntlTestHelper::requireFullIntl($this, '59.1'); + \Locale::setDefault('de_AT'); + $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::FULL); - $this->assertEquals('03.02.2010, 04:05:06 GMT', $transformer->transform($this->dateTime)); + $this->assertEquals('03.02.2010, 04:05:06 Koordinierte Weltzeit', $transformer->transform($this->dateTime)); } public function testTransformToDifferentLocale() diff --git a/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimeZoneTransformer.php b/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimeZoneTransformer.php index 65d22dbe39..5170bb788e 100644 --- a/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimeZoneTransformer.php +++ b/src/Symfony/Component/Intl/DateFormatter/DateFormat/TimeZoneTransformer.php @@ -33,10 +33,29 @@ class TimeZoneTransformer extends Transformer throw new NotImplementedException('Time zone different than GMT or UTC is not supported as a formatting output.'); } - // From ICU >= 4.8, the zero offset is not more used, example: GMT instead of GMT+00:00 - $format = (0 !== (int) $dateTime->format('O')) ? '\G\M\TP' : '\G\M\T'; + if ('Etc' === $timeZone) { + // i.e. Etc/GMT+1, Etc/UTC, Etc/Zulu + $timeZone = substr($dateTime->getTimezone()->getName(), 4); + } - return $dateTime->format($format); + // From ICU >= 59.1 GMT and UTC are no longer unified + if (in_array($timeZone, array('UTC', 'UCT', 'Universal', 'Zulu'))) { + // offset is not supported with UTC + return $length > 3 ? 'Coordinated Universal Time' : 'UTC'; + } + + $offset = (int) $dateTime->format('O'); + + // From ICU >= 4.8, the zero offset is no more used, example: GMT instead of GMT+00:00 + if (0 === $offset) { + return $length > 3 ? 'Greenwich Mean Time' : 'GMT'; + } + + if ($length > 3) { + return $dateTime->format('\G\M\TP'); + } + + return sprintf('GMT%s%d', ($offset >= 0 ? '+' : ''), $offset / 100); } /** diff --git a/src/Symfony/Component/Intl/README.md b/src/Symfony/Component/Intl/README.md index 30cb5093c4..806c6275e8 100644 --- a/src/Symfony/Component/Intl/README.md +++ b/src/Symfony/Component/Intl/README.md @@ -15,5 +15,7 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) + * [Docker images with intl support](https://hub.docker.com/r/jakzal/php-intl) + (for the Intl component development) [0]: http://www.php.net/manual/en/intl.setup.php diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php index 88cc55e9be..ee8b5c9447 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/AbstractIntlDateFormatterTest.php @@ -231,15 +231,8 @@ abstract class AbstractIntlDateFormatterTest extends TestCase array('s', 43200, '0'), // 12 hours // general - array("yyyy.MM.dd 'at' HH:mm:ss zzz", 0, '1970.01.01 at 00:00:00 GMT'), - array('K:mm a, z', 0, '0:00 AM, GMT'), - - // timezone - array('z', 0, 'GMT'), - array('zz', 0, 'GMT'), - array('zzz', 0, 'GMT'), - array('zzzz', 0, 'GMT'), - array('zzzzz', 0, 'GMT'), + array("yyyy.MM.dd 'at' HH:mm:ss zzz", 0, '1970.01.01 at 00:00:00 UTC'), + array('K:mm a, z', 0, '0:00 AM, UTC'), ); $dateTime = new \DateTime('@0'); @@ -250,12 +243,25 @@ abstract class AbstractIntlDateFormatterTest extends TestCase $formatData[] = array('h:mm a', $dateTime, '12:00 AM'); $formatData[] = array('yyyyy.MMMM.dd hh:mm aaa', $dateTime, '01970.January.01 12:00 AM'); - $formatData[] = array("yyyy.MM.dd 'at' HH:mm:ss zzz", $dateTime, '1970.01.01 at 00:00:00 GMT'); - $formatData[] = array('K:mm a, z', $dateTime, '0:00 AM, GMT'); + $formatData[] = array("yyyy.MM.dd 'at' HH:mm:ss zzz", $dateTime, '1970.01.01 at 00:00:00 UTC'); + $formatData[] = array('K:mm a, z', $dateTime, '0:00 AM, UTC'); return $formatData; } + /** + * @requires PHP 5.5.10 + */ + public function testFormatUtcAndGmtAreSplit() + { + $pattern = "yyyy.MM.dd 'at' HH:mm:ss zzz"; + $gmtFormatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'GMT', IntlDateFormatter::GREGORIAN, $pattern); + $utcFormatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'UTC', IntlDateFormatter::GREGORIAN, $pattern); + + $this->assertSame('1970.01.01 at 00:00:00 GMT', $gmtFormatter->format(new \DateTime('@0'))); + $this->assertSame('1970.01.01 at 00:00:00 UTC', $utcFormatter->format(new \DateTime('@0'))); + } + /** * @dataProvider formatErrorProvider */ @@ -334,6 +340,75 @@ abstract class AbstractIntlDateFormatterTest extends TestCase return $data; } + /** + * @dataProvider formatTimezoneProvider + * @requires PHP 5.5 + */ + public function testFormatTimezone($pattern, $timezone, $expected) + { + $formatter = $this->getDefaultDateFormatter($pattern); + $formatter->setTimeZone(new \DateTimeZone($timezone)); + + $this->assertEquals($expected, $formatter->format(0)); + } + + public function formatTimezoneProvider() + { + $cases = array( + array('z', 'GMT', 'GMT'), + array('zz', 'GMT', 'GMT'), + array('zzz', 'GMT', 'GMT'), + array('zzzz', 'GMT', 'Greenwich Mean Time'), + array('zzzzz', 'GMT', 'Greenwich Mean Time'), + + array('z', 'Etc/GMT', 'GMT'), + array('zz', 'Etc/GMT', 'GMT'), + array('zzz', 'Etc/GMT', 'GMT'), + array('zzzz', 'Etc/GMT', 'Greenwich Mean Time'), + array('zzzzz', 'Etc/GMT', 'Greenwich Mean Time'), + + array('z', 'Etc/GMT+3', 'GMT-3'), + array('zz', 'Etc/GMT+3', 'GMT-3'), + array('zzz', 'Etc/GMT+3', 'GMT-3'), + array('zzzz', 'Etc/GMT+3', 'GMT-03:00'), + array('zzzzz', 'Etc/GMT+3', 'GMT-03:00'), + + array('z', 'UTC', 'UTC'), + array('zz', 'UTC', 'UTC'), + array('zzz', 'UTC', 'UTC'), + array('zzzz', 'UTC', 'Coordinated Universal Time'), + array('zzzzz', 'UTC', 'Coordinated Universal Time'), + + array('z', 'Etc/UTC', 'UTC'), + array('zz', 'Etc/UTC', 'UTC'), + array('zzz', 'Etc/UTC', 'UTC'), + array('zzzz', 'Etc/UTC', 'Coordinated Universal Time'), + array('zzzzz', 'Etc/UTC', 'Coordinated Universal Time'), + + array('z', 'Etc/Universal', 'UTC'), + array('z', 'Etc/Zulu', 'UTC'), + array('z', 'Etc/UCT', 'UTC'), + array('z', 'Etc/Greenwich', 'GMT'), + array('zzzzz', 'Etc/Universal', 'Coordinated Universal Time'), + array('zzzzz', 'Etc/Zulu', 'Coordinated Universal Time'), + array('zzzzz', 'Etc/UCT', 'Coordinated Universal Time'), + array('zzzzz', 'Etc/Greenwich', 'Greenwich Mean Time'), + ); + + if (!defined('HHVM_VERSION')) { + // these timezones are not considered valid in HHVM + $cases = array_merge($cases, array( + array('z', 'GMT+03:00', 'GMT+3'), + array('zz', 'GMT+03:00', 'GMT+3'), + array('zzz', 'GMT+03:00', 'GMT+3'), + array('zzzz', 'GMT+03:00', 'GMT+03:00'), + array('zzzzz', 'GMT+03:00', 'GMT+03:00'), + )); + } + + return $cases; + } + public function testFormatWithGmtTimezone() { $formatter = $this->getDefaultDateFormatter('zzzz'); @@ -389,8 +464,11 @@ abstract class AbstractIntlDateFormatterTest extends TestCase if (PHP_VERSION_ID < 50500 && !(extension_loaded('intl') && method_exists('IntlDateFormatter', 'setTimeZone'))) { $this->markTestSkipped('Only in PHP 5.5+ IntlDateFormatter allows to use DateTimeZone objects.'); } + if (PHP_VERSION_ID < 50510) { + $this->markTestSkipped('Before PHP 5.5.10 the GMT timezone used to be converted to UTC.'); + } - $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzzz'); + $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzz'); $this->assertEquals('GMT', $formatter->format(0)); } @@ -482,8 +560,8 @@ abstract class AbstractIntlDateFormatterTest extends TestCase array(0, IntlDateFormatter::LONG, IntlDateFormatter::NONE, 'January 1, 1970'), array(0, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, 'Jan 1, 1970'), array(0, IntlDateFormatter::SHORT, IntlDateFormatter::NONE, '1/1/70'), - array(0, IntlDateFormatter::NONE, IntlDateFormatter::FULL, '12:00:00 AM GMT'), - array(0, IntlDateFormatter::NONE, IntlDateFormatter::LONG, '12:00:00 AM GMT'), + array(0, IntlDateFormatter::NONE, IntlDateFormatter::FULL, '12:00:00 AM Coordinated Universal Time'), + array(0, IntlDateFormatter::NONE, IntlDateFormatter::LONG, '12:00:00 AM UTC'), array(0, IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM, '12:00:00 AM'), array(0, IntlDateFormatter::NONE, IntlDateFormatter::SHORT, '12:00 AM'), ); diff --git a/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php b/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php index 1754a08438..45f8ee5404 100644 --- a/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php +++ b/src/Symfony/Component/Intl/Tests/DateFormatter/Verification/IntlDateFormatterTest.php @@ -43,6 +43,34 @@ class IntlDateFormatterTest extends AbstractIntlDateFormatterTest parent::testFormatWithTimezoneFromEnvironmentVariable(); } + /** + * @dataProvider formatTimezoneProvider + * @requires PHP 5.5 + */ + public function testFormatTimezone($pattern, $timezone, $expected) + { + IntlTestHelper::requireFullIntl($this, '59.1'); + + parent::testFormatTimezone($pattern, $timezone, $expected); + } + + public function testFormatUtcAndGmtAreSplit() + { + IntlTestHelper::requireFullIntl($this, '59.1'); + + parent::testFormatUtcAndGmtAreSplit(); + } + + /** + * @dataProvider dateAndTimeTypeProvider + */ + public function testDateAndTimeType($timestamp, $datetype, $timetype, $expected) + { + IntlTestHelper::requireFullIntl($this, '59.1'); + + parent::testDateAndTimeType($timestamp, $datetype, $timetype, $expected); + } + protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null) { IntlTestHelper::requireFullIntl($this, '55.1');