From: =?utf-8?q?David_Pr=C3=A9vot?= <taffit@debian.org>
Date: Sun, 4 May 2025 10:57:53 +0200
Subject: =?utf-8?q?Rename_Abstract=E2=80=A6Test_into_Abstract=E2=80=A6TestC?=
 =?utf-8?q?ase?=

---
 tests/Intl/Icu/AbstractCollatorTest.php            |   60 --
 tests/Intl/Icu/AbstractCollatorTestCase.php        |   60 ++
 tests/Intl/Icu/AbstractIcuTest.php                 |   43 -
 tests/Intl/Icu/AbstractIcuTestCase.php             |   43 +
 tests/Intl/Icu/AbstractIntlDateFormatterTest.php   | 1032 --------------------
 .../Intl/Icu/AbstractIntlDateFormatterTestCase.php | 1032 ++++++++++++++++++++
 tests/Intl/Icu/AbstractLocaleTest.php              |   31 -
 tests/Intl/Icu/AbstractLocaleTestCase.php          |   31 +
 tests/Intl/Icu/AbstractNumberFormatterTest.php     |  899 -----------------
 tests/Intl/Icu/AbstractNumberFormatterTestCase.php |  899 +++++++++++++++++
 tests/Intl/Icu/CollatorTest.php                    |    3 +-
 tests/Intl/Icu/IcuTest.php                         |    3 +-
 tests/Intl/Icu/IntlDateFormatterTest.php           |    3 +-
 tests/Intl/Icu/LocaleTest.php                      |    2 +-
 tests/Intl/Icu/NumberFormatterTest.php             |    2 +-
 tests/Intl/Icu/Verification/CollatorTest.php       |    4 +-
 tests/Intl/Icu/Verification/IcuTest.php            |    4 +-
 .../Icu/Verification/IntlDateFormatterTest.php     |    4 +-
 tests/Intl/Icu/Verification/LocaleTest.php         |    4 +-
 .../Intl/Icu/Verification/NumberFormatterTest.php  |    4 +-
 20 files changed, 2083 insertions(+), 2080 deletions(-)
 delete mode 100644 tests/Intl/Icu/AbstractCollatorTest.php
 create mode 100644 tests/Intl/Icu/AbstractCollatorTestCase.php
 delete mode 100644 tests/Intl/Icu/AbstractIcuTest.php
 create mode 100644 tests/Intl/Icu/AbstractIcuTestCase.php
 delete mode 100644 tests/Intl/Icu/AbstractIntlDateFormatterTest.php
 create mode 100644 tests/Intl/Icu/AbstractIntlDateFormatterTestCase.php
 delete mode 100644 tests/Intl/Icu/AbstractLocaleTest.php
 create mode 100644 tests/Intl/Icu/AbstractLocaleTestCase.php
 delete mode 100644 tests/Intl/Icu/AbstractNumberFormatterTest.php
 create mode 100644 tests/Intl/Icu/AbstractNumberFormatterTestCase.php

diff --git a/tests/Intl/Icu/AbstractCollatorTest.php b/tests/Intl/Icu/AbstractCollatorTest.php
deleted file mode 100644
index 166741b..0000000
--- a/tests/Intl/Icu/AbstractCollatorTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?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\Polyfill\Tests\Intl\Icu;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Polyfill\Intl\Icu\Collator;
-
-/**
- * Test case for Collator implementations.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
-abstract class AbstractCollatorTest extends TestCase
-{
-    /**
-     * @dataProvider asortProvider
-     */
-    public function testAsort($array, $sortFlag, $expected)
-    {
-        $collator = $this->getCollator('en');
-        $collator->asort($array, $sortFlag);
-        $this->assertSame($expected, $array);
-    }
-
-    public static function asortProvider()
-    {
-        return [
-            /* array, sortFlag, expected */
-            [
-                ['a', 'b', 'c'],
-                Collator::SORT_REGULAR,
-                ['a', 'b', 'c'],
-            ],
-            [
-                ['c', 'b', 'a'],
-                Collator::SORT_REGULAR,
-                [2 => 'a', 1 => 'b',  0 => 'c'],
-            ],
-            [
-                ['b', 'c', 'a'],
-                Collator::SORT_REGULAR,
-                [2 => 'a', 0 => 'b', 1 => 'c'],
-            ],
-        ];
-    }
-
-    /**
-     * @return Collator|\Collator
-     */
-    abstract protected function getCollator(string $locale);
-}
diff --git a/tests/Intl/Icu/AbstractCollatorTestCase.php b/tests/Intl/Icu/AbstractCollatorTestCase.php
new file mode 100644
index 0000000..d8bbfa9
--- /dev/null
+++ b/tests/Intl/Icu/AbstractCollatorTestCase.php
@@ -0,0 +1,60 @@
+<?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\Polyfill\Tests\Intl\Icu;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Polyfill\Intl\Icu\Collator;
+
+/**
+ * Test case for Collator implementations.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+abstract class AbstractCollatorTestCase extends TestCase
+{
+    /**
+     * @dataProvider asortProvider
+     */
+    public function testAsort($array, $sortFlag, $expected)
+    {
+        $collator = $this->getCollator('en');
+        $collator->asort($array, $sortFlag);
+        $this->assertSame($expected, $array);
+    }
+
+    public static function asortProvider()
+    {
+        return [
+            /* array, sortFlag, expected */
+            [
+                ['a', 'b', 'c'],
+                Collator::SORT_REGULAR,
+                ['a', 'b', 'c'],
+            ],
+            [
+                ['c', 'b', 'a'],
+                Collator::SORT_REGULAR,
+                [2 => 'a', 1 => 'b',  0 => 'c'],
+            ],
+            [
+                ['b', 'c', 'a'],
+                Collator::SORT_REGULAR,
+                [2 => 'a', 0 => 'b', 1 => 'c'],
+            ],
+        ];
+    }
+
+    /**
+     * @return Collator|\Collator
+     */
+    abstract protected function getCollator(string $locale);
+}
diff --git a/tests/Intl/Icu/AbstractIcuTest.php b/tests/Intl/Icu/AbstractIcuTest.php
deleted file mode 100644
index b676ca0..0000000
--- a/tests/Intl/Icu/AbstractIcuTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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\Polyfill\Tests\Intl\Icu;
-
-use PHPUnit\Framework\TestCase;
-
-/**
- * Test case for intl function implementations.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
-abstract class AbstractIcuTest extends TestCase
-{
-    public static function errorNameProvider()
-    {
-        return [
-            [-129, '[BOGUS UErrorCode]'],
-            [0, 'U_ZERO_ERROR'],
-            [1, 'U_ILLEGAL_ARGUMENT_ERROR'],
-            [9, 'U_PARSE_ERROR'],
-            [129, '[BOGUS UErrorCode]'],
-        ];
-    }
-
-    /**
-     * @dataProvider errorNameProvider
-     */
-    public function testGetErrorName($errorCode, $errorName)
-    {
-        $this->assertSame($errorName, $this->getIntlErrorName($errorCode));
-    }
-
-    abstract protected function getIntlErrorName($errorCode);
-}
diff --git a/tests/Intl/Icu/AbstractIcuTestCase.php b/tests/Intl/Icu/AbstractIcuTestCase.php
new file mode 100644
index 0000000..eee2c50
--- /dev/null
+++ b/tests/Intl/Icu/AbstractIcuTestCase.php
@@ -0,0 +1,43 @@
+<?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\Polyfill\Tests\Intl\Icu;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test case for intl function implementations.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+abstract class AbstractIcuTestCase extends TestCase
+{
+    public static function errorNameProvider()
+    {
+        return [
+            [-129, '[BOGUS UErrorCode]'],
+            [0, 'U_ZERO_ERROR'],
+            [1, 'U_ILLEGAL_ARGUMENT_ERROR'],
+            [9, 'U_PARSE_ERROR'],
+            [129, '[BOGUS UErrorCode]'],
+        ];
+    }
+
+    /**
+     * @dataProvider errorNameProvider
+     */
+    public function testGetErrorName($errorCode, $errorName)
+    {
+        $this->assertSame($errorName, $this->getIntlErrorName($errorCode));
+    }
+
+    abstract protected function getIntlErrorName($errorCode);
+}
diff --git a/tests/Intl/Icu/AbstractIntlDateFormatterTest.php b/tests/Intl/Icu/AbstractIntlDateFormatterTest.php
deleted file mode 100644
index 7761353..0000000
--- a/tests/Intl/Icu/AbstractIntlDateFormatterTest.php
+++ /dev/null
@@ -1,1032 +0,0 @@
-<?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\Polyfill\Tests\Intl\Icu;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Polyfill\Intl\Icu\Icu;
-use Symfony\Polyfill\Intl\Icu\Intl;
-use Symfony\Polyfill\Intl\Icu\IntlDateFormatter;
-
-/**
- * Test case for IntlDateFormatter implementations.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
-abstract class AbstractIntlDateFormatterTest extends TestCase
-{
-    protected function setUp(): void
-    {
-        \Locale::setDefault('en');
-    }
-
-    /**
-     * When a time zone is not specified, it uses the system default however it returns null in the getter method.
-     */
-    public function testConstructorDefaultTimeZone()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
-
-        $this->assertSame(date_default_timezone_get(), $formatter->getTimeZoneId());
-
-        $this->assertSame(
-            $this->getDateTime(0, $formatter->getTimeZoneId())->format('M j, Y, g:i A'),
-            str_replace("\u{202F}", ' ', $formatter->format(0))
-        );
-    }
-
-    /**
-     * @group legacy
-     */
-    public function testConstructorWithoutDateType()
-    {
-        $formatter = $this->getDateFormatter('en', null, IntlDateFormatter::SHORT, 'UTC', IntlDateFormatter::GREGORIAN);
-
-        $this->assertSame('EEEE, MMMM d, y \'at\' h:mm a', str_replace("\u{202F}", ' ', $formatter->getPattern()));
-    }
-
-    /**
-     * @group legacy
-     */
-    public function testConstructorWithoutTimeType()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::SHORT, null, 'UTC', IntlDateFormatter::GREGORIAN);
-
-        $this->assertSame('M/d/yy, h:mm:ss a zzzz', str_replace("\u{202F}", ' ', $formatter->getPattern()));
-    }
-
-    /**
-     * @dataProvider formatProvider
-     */
-    public function testFormat($pattern, $timestamp, $expected)
-    {
-        $errorCode = Icu::U_ZERO_ERROR;
-        $errorMessage = 'U_ZERO_ERROR';
-
-        $formatter = $this->getDefaultDateFormatter($pattern);
-        $this->assertSame($expected, $formatter->format($timestamp));
-        $this->assertIsIntlSuccess($formatter, $errorMessage, $errorCode);
-    }
-
-    public static function formatProvider()
-    {
-        $dateTime = new \DateTime('@0');
-        $dateTimeImmutable = new \DateTimeImmutable('@0');
-
-        /* https://unicode-org.atlassian.net/browse/ICU-21647 */
-        $expectedQuarterX5 = '1';
-        if (\defined('INTL_ICU_VERSION') && version_compare(\INTL_ICU_VERSION, '70.1', '<')) {
-            $expectedQuarterX5 = '1st quarter';
-        }
-
-        $formatData = [
-            /* general */
-            ['y-M-d', 0, '1970-1-1'],
-            ["EEE, MMM d, ''yy", 0, "Thu, Jan 1, '70"],
-            ['h:mm a', 0, '12:00 AM'],
-            ['yyyyy.MMMM.dd hh:mm aaa', 0, '01970.January.01 12:00 AM'],
-
-            /* escaping */
-            ["'M'", 0, 'M'],
-            ["'yy'", 0, 'yy'],
-            ["'''yy'", 0, "'yy"],
-            ["''y", 0, "'1970"],
-            ["''yy", 0, "'70"],
-            ["H 'o'' clock'", 0, "0 o' clock"],
-
-            /* month */
-            ['M', 0, '1'],
-            ['MM', 0, '01'],
-            ['MMM', 0, 'Jan'],
-            ['MMMM', 0, 'January'],
-            ['MMMMM', 0, 'J'],
-            ['MMMMMM', 0, '000001'],
-
-            ['L', 0, '1'],
-            ['LL', 0, '01'],
-            ['LLL', 0, 'Jan'],
-            ['LLLL', 0, 'January'],
-            ['LLLLL', 0, 'J'],
-            ['LLLLLL', 0, '000001'],
-
-            /* year */
-            ['y', 0, '1970'],
-            ['yy', 0, '70'],
-            ['yyy', 0, '1970'],
-            ['yyyy', 0, '1970'],
-            ['yyyyy', 0, '01970'],
-            ['yyyyyy', 0, '001970'],
-
-            /* day */
-            ['d', 0, '1'],
-            ['dd', 0, '01'],
-            ['ddd', 0, '001'],
-
-            /* quarter */
-            ['Q', 0, '1'],
-            ['QQ', 0, '01'],
-            ['QQQ', 0, 'Q1'],
-            ['QQQQ', 0, '1st quarter'],
-            ['QQQQQ', 0, $expectedQuarterX5],
-
-            ['q', 0, '1'],
-            ['qq', 0, '01'],
-            ['qqq', 0, 'Q1'],
-            ['qqqq', 0, '1st quarter'],
-            ['qqqqq', 0, $expectedQuarterX5],
-
-            // 4 months
-            ['Q', 7776000, '2'],
-            ['QQ', 7776000, '02'],
-            ['QQQ', 7776000, 'Q2'],
-            ['QQQQ', 7776000, '2nd quarter'],
-
-            // 7 months
-            ['QQQQ', 15638400, '3rd quarter'],
-
-            // 10 months
-            ['QQQQ', 23587200, '4th quarter'],
-
-            /* 12-hour (1-12) */
-            ['h', 0, '12'],
-            ['hh', 0, '12'],
-            ['hhh', 0, '012'],
-
-            ['h', 1, '12'],
-            ['h', 3600, '1'],
-            ['h', 43200, '12'], // 12 hours
-
-            /* day of year */
-            ['D', 0, '1'],
-            ['D', 86400, '2'], // 1 day
-            ['D', 31536000, '1'], // 1 year
-            ['D', 31622400, '2'], // 1 year + 1 day
-
-            /* day of week */
-            ['E', 0, 'Thu'],
-            ['EE', 0, 'Thu'],
-            ['EEE', 0, 'Thu'],
-            ['EEEE', 0, 'Thursday'],
-            ['EEEEE', 0, 'T'],
-            ['EEEEEE', 0, 'Th'],
-
-            ['E', 1296540000, 'Tue'], // 2011-02-01
-            ['E', 1296950400, 'Sun'], // 2011-02-06
-
-            /* am/pm marker */
-            ['a', 0, 'AM'],
-            ['aa', 0, 'AM'],
-            ['aaa', 0, 'AM'],
-            ['aaaa', 0, 'AM'],
-
-            // 12 hours
-            ['a', 43200, 'PM'],
-            ['aa', 43200, 'PM'],
-            ['aaa', 43200, 'PM'],
-            ['aaaa', 43200, 'PM'],
-
-            /* 24-hour (0-23) */
-            ['H', 0, '0'],
-            ['HH', 0, '00'],
-            ['HHH', 0, '000'],
-
-            ['H', 1, '0'],
-            ['H', 3600, '1'],
-            ['H', 43200, '12'],
-            ['H', 46800, '13'],
-
-            /* 24-hour (1-24) */
-            ['k', 0, '24'],
-            ['kk', 0, '24'],
-            ['kkk', 0, '024'],
-
-            ['k', 1, '24'],
-            ['k', 3600, '1'],
-            ['k', 43200, '12'],
-            ['k', 46800, '13'],
-
-            /* 12-hour (0-11) */
-            ['K', 0, '0'],
-            ['KK', 0, '00'],
-            ['KKK', 0, '000'],
-
-            ['K', 1, '0'],
-            ['K', 3600, '1'],
-            ['K', 43200, '0'], // 12 hours
-
-            /* minute */
-            ['m', 0, '0'],
-            ['mm', 0, '00'],
-            ['mmm', 0, '000'],
-
-            ['m', 1, '0'],
-            ['m', 60, '1'],
-            ['m', 120, '2'],
-            ['m', 180, '3'],
-            ['m', 3600, '0'],
-            ['m', 3660, '1'],
-            ['m', 43200, '0'], // 12 hours
-
-            /* second */
-            ['s', 0, '0'],
-            ['ss', 0, '00'],
-            ['sss', 0, '000'],
-
-            ['s', 1, '1'],
-            ['s', 2, '2'],
-            ['s', 5, '5'],
-            ['s', 30, '30'],
-            ['s', 59, '59'],
-            ['s', 60, '0'],
-            ['s', 120, '0'],
-            ['s', 180, '0'],
-            ['s', 3600, '0'],
-            ['s', 3601, '1'],
-            ['s', 3630, '30'],
-            ['s', 43200, '0'], // 12 hours
-        ];
-
-        /* general, DateTime */
-        $formatData[] = ['y-M-d', $dateTime, '1970-1-1'];
-        $formatData[] = ["EEE, MMM d, ''yy", $dateTime, "Thu, Jan 1, '70"];
-        $formatData[] = ['h:mm a', $dateTime, '12:00 AM'];
-        $formatData[] = ['yyyyy.MMMM.dd hh:mm aaa', $dateTime, '01970.January.01 12:00 AM'];
-
-        /* general, DateTimeImmutable */
-        $formatData[] = ['y-M-d', $dateTimeImmutable, '1970-1-1'];
-        $formatData[] = ["EEE, MMM d, ''yy", $dateTimeImmutable, "Thu, Jan 1, '70"];
-        $formatData[] = ['h:mm a', $dateTimeImmutable, '12:00 AM'];
-        $formatData[] = ['yyyyy.MMMM.dd hh:mm aaa', $dateTimeImmutable, '01970.January.01 12:00 AM'];
-
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '59.1', '>=')) {
-            // Before ICU 59.1 GMT was used instead of UTC
-            $formatData[] = ["yyyy.MM.dd 'at' HH:mm:ss zzz", 0, '1970.01.01 at 00:00:00 UTC'];
-            $formatData[] = ['K:mm a, z', 0, '0:00 AM, UTC'];
-            $formatData[] = ["yyyy.MM.dd 'at' HH:mm:ss zzz", $dateTime, '1970.01.01 at 00:00:00 UTC'];
-            $formatData[] = ['K:mm a, z', $dateTime, '0:00 AM, UTC'];
-        }
-
-        return $formatData;
-    }
-
-    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')));
-        $this->assertSame('1970.01.01 at 00:00:00 GMT', $gmtFormatter->format(new \DateTimeImmutable('@0')));
-        $this->assertSame('1970.01.01 at 00:00:00 UTC', $utcFormatter->format(new \DateTimeImmutable('@0')));
-    }
-
-    /**
-     * @dataProvider formatErrorProvider
-     */
-    public function testFormatIllegalArgumentError($pattern, $timestamp, $errorMessage)
-    {
-        $errorCode = Icu::U_ILLEGAL_ARGUMENT_ERROR;
-
-        $formatter = $this->getDefaultDateFormatter($pattern);
-        $this->assertFalse($formatter->format($timestamp));
-        $this->assertIsIntlFailure($formatter, $errorMessage, $errorCode);
-    }
-
-    public static function formatErrorProvider()
-    {
-        return [
-            ['y-M-d', 'foobar', 'datefmt_format: string \'foobar\' is not numeric, which would be required for it to be a valid date: U_ILLEGAL_ARGUMENT_ERROR'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatWithTimezoneProvider
-     */
-    public function testFormatWithTimezone($timestamp, $timezone, $expected)
-    {
-        $pattern = 'yyyy-MM-dd HH:mm:ss';
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, $timezone, IntlDateFormatter::GREGORIAN, $pattern);
-        $this->assertSame($expected, $formatter->format($timestamp));
-    }
-
-    public static function formatWithTimezoneProvider()
-    {
-        $data = [
-            [0, 'UTC', '1970-01-01 00:00:00'],
-            [0, 'GMT', '1970-01-01 00:00:00'],
-            [0, 'GMT-03:00', '1969-12-31 21:00:00'],
-            [0, 'GMT+03:00', '1970-01-01 03:00:00'],
-            [0, 'Europe/Zurich', '1970-01-01 01:00:00'],
-            [0, 'Europe/Paris', '1970-01-01 01:00:00'],
-            [0, 'Africa/Cairo', '1970-01-01 02:00:00'],
-            [0, 'Africa/Casablanca', '1970-01-01 00:00:00'],
-            [0, 'Africa/Djibouti', '1970-01-01 03:00:00'],
-            [0, 'Africa/Johannesburg', '1970-01-01 02:00:00'],
-            [0, 'America/Antigua', '1969-12-31 20:00:00'],
-            [0, 'America/Toronto', '1969-12-31 19:00:00'],
-            [0, 'America/Vancouver', '1969-12-31 16:00:00'],
-            [0, 'Asia/Aqtau', '1970-01-01 05:00:00'],
-            [0, 'Asia/Bangkok', '1970-01-01 07:00:00'],
-            [0, 'Asia/Dubai', '1970-01-01 04:00:00'],
-            [0, 'Australia/Brisbane', '1970-01-01 10:00:00'],
-            [0, 'Australia/Eucla', '1970-01-01 08:45:00'],
-            [0, 'Australia/Melbourne', '1970-01-01 10:00:00'],
-            [0, 'Europe/Berlin', '1970-01-01 01:00:00'],
-            [0, 'Europe/Dublin', '1970-01-01 01:00:00'],
-            [0, 'Europe/Warsaw', '1970-01-01 01:00:00'],
-            [0, 'Pacific/Fiji', '1970-01-01 12:00:00'],
-        ];
-
-        return $data;
-    }
-
-    /**
-     * @dataProvider formatTimezoneProvider
-     */
-    public function testFormatTimezone($pattern, $timezone, $expected)
-    {
-        $formatter = $this->getDefaultDateFormatter($pattern);
-        $formatter->setTimeZone(new \DateTimeZone($timezone));
-
-        $this->assertEquals($expected, $formatter->format(0));
-    }
-
-    public static function formatTimezoneProvider()
-    {
-        return [
-            ['z', 'GMT', 'GMT'],
-            ['zz', 'GMT', 'GMT'],
-            ['zzz', 'GMT', 'GMT'],
-            ['zzzz', 'GMT', 'Greenwich Mean Time'],
-            ['zzzzz', 'GMT', 'Greenwich Mean Time'],
-
-            ['z', 'Etc/GMT', 'GMT'],
-            ['zz', 'Etc/GMT', 'GMT'],
-            ['zzz', 'Etc/GMT', 'GMT'],
-            ['zzzz', 'Etc/GMT', 'Greenwich Mean Time'],
-            ['zzzzz', 'Etc/GMT', 'Greenwich Mean Time'],
-
-            ['z', 'Etc/GMT+3', 'GMT-3'],
-            ['zz', 'Etc/GMT+3', 'GMT-3'],
-            ['zzz', 'Etc/GMT+3', 'GMT-3'],
-            ['zzzz', 'Etc/GMT+3', 'GMT-03:00'],
-            ['zzzzz', 'Etc/GMT+3', 'GMT-03:00'],
-
-            ['z', 'UTC', 'UTC'],
-            ['zz', 'UTC', 'UTC'],
-            ['zzz', 'UTC', 'UTC'],
-            ['zzzz', 'UTC', 'Coordinated Universal Time'],
-            ['zzzzz', 'UTC', 'Coordinated Universal Time'],
-
-            ['z', 'Etc/UTC', 'UTC'],
-            ['zz', 'Etc/UTC', 'UTC'],
-            ['zzz', 'Etc/UTC', 'UTC'],
-            ['zzzz', 'Etc/UTC', 'Coordinated Universal Time'],
-            ['zzzzz', 'Etc/UTC', 'Coordinated Universal Time'],
-
-            ['z', 'Etc/Universal', 'UTC'],
-            ['z', 'Etc/Zulu', 'UTC'],
-            ['z', 'Etc/UCT', 'UTC'],
-            ['z', 'Etc/Greenwich', 'GMT'],
-            ['zzzzz', 'Etc/Universal', 'Coordinated Universal Time'],
-            ['zzzzz', 'Etc/Zulu', 'Coordinated Universal Time'],
-            ['zzzzz', 'Etc/UCT', 'Coordinated Universal Time'],
-            ['zzzzz', 'Etc/Greenwich', 'Greenwich Mean Time'],
-
-            ['z', 'GMT+03:00', 'GMT+3'],
-            ['zz', 'GMT+03:00', 'GMT+3'],
-            ['zzz', 'GMT+03:00', 'GMT+3'],
-            ['zzzz', 'GMT+03:00', 'GMT+03:00'],
-            ['zzzzz', 'GMT+03:00', 'GMT+03:00'],
-        ];
-    }
-
-    public function testFormatWithGmtTimezone()
-    {
-        $formatter = $this->getDefaultDateFormatter('zzzz');
-
-        $formatter->setTimeZone('GMT+03:00');
-
-        $this->assertEquals('GMT+03:00', $formatter->format(0));
-    }
-
-    public function testFormatWithGmtTimeZoneAndMinutesOffset()
-    {
-        $formatter = $this->getDefaultDateFormatter('zzzz');
-
-        $formatter->setTimeZone('GMT+00:30');
-
-        $this->assertEquals('GMT+00:30', $formatter->format(0));
-    }
-
-    public function testFormatWithNonStandardTimezone()
-    {
-        $formatter = $this->getDefaultDateFormatter('zzzz');
-
-        $formatter->setTimeZone('Pacific/Fiji');
-
-        $this->assertEquals('Fiji Standard Time', $formatter->format(0));
-    }
-
-    public function testFormatWithConstructorTimezone()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'UTC');
-        $formatter->setPattern('yyyy-MM-dd HH:mm:ss');
-
-        $this->assertEquals(
-            $this->getDateTime(0, 'UTC')->format('Y-m-d H:i:s'),
-            $formatter->format(0)
-        );
-    }
-
-    public function testFormatWithDateTimeZoneGmt()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzz');
-
-        $this->assertEquals('GMT', $formatter->format(0));
-    }
-
-    public function testFormatWithDateTimeZoneGmtOffset()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
-
-        $this->assertEquals('GMT+03:00', $formatter->format(0));
-    }
-
-    /**
-     * @requires extension intl
-     */
-    public function testFormatWithIntlTimeZone()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, \IntlTimeZone::createTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
-
-        $this->assertEquals('GMT+03:00', $formatter->format(0));
-    }
-
-    public function testFormatWithTimezoneFromPhp()
-    {
-        $tz = date_default_timezone_get();
-        date_default_timezone_set('Europe/London');
-
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
-        $formatter->setPattern('yyyy-MM-dd HH:mm:ss');
-
-        $this->assertEquals(
-            $this->getDateTime(0, 'Europe/London')->format('Y-m-d H:i:s'),
-            $formatter->format(0)
-        );
-
-        $this->assertEquals('Europe/London', date_default_timezone_get());
-
-        // Restores TZ.
-        date_default_timezone_set($tz);
-    }
-
-    public function testFormatIgnoresPatternForRelativeDateType()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::FULL, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzzz');
-
-        $datetime = \DateTime::createFromFormat('U', time(), new \DateTimeZone('GMT'));
-        $datetime->setTime(0, 0, 0);
-
-        $formatted = $formatter->format($datetime);
-        $formatted = str_replace(' at ', ', ', $formatted);
-        $formatted = str_replace("\u{202F}", ' ', $formatted);
-
-        $this->assertSame('today, 12:00:00 AM Greenwich Mean Time', $formatted);
-    }
-
-    /**
-     * @dataProvider dateAndTimeTypeProvider
-     */
-    public function testDateAndTimeType($timestamp, $datetype, $timetype, $expected)
-    {
-        $formatter = $this->getDateFormatter('en', $datetype, $timetype, 'UTC');
-        $this->assertSame($expected, str_replace("\u{202F}", ' ', $formatter->format($timestamp)));
-    }
-
-    public static function dateAndTimeTypeProvider()
-    {
-        return [
-            [0, IntlDateFormatter::FULL, IntlDateFormatter::NONE, 'Thursday, January 1, 1970'],
-            [0, IntlDateFormatter::LONG, IntlDateFormatter::NONE, 'January 1, 1970'],
-            [0, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, 'Jan 1, 1970'],
-            [0, IntlDateFormatter::SHORT, IntlDateFormatter::NONE, '1/1/70'],
-            [0, IntlDateFormatter::NONE, IntlDateFormatter::FULL, '12:00:00 AM Coordinated Universal Time'],
-            [0, IntlDateFormatter::NONE, IntlDateFormatter::LONG, '12:00:00 AM UTC'],
-            [0, IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM, '12:00:00 AM'],
-            [0, IntlDateFormatter::NONE, IntlDateFormatter::SHORT, '12:00 AM'],
-        ];
-    }
-
-    /**
-     * @dataProvider relativeDateTypeProvider
-     */
-    public function testRelativeDateType($timestamp, $datetype, $timetype, $expected)
-    {
-        $datetime = \DateTime::createFromFormat('U', $timestamp, new \DateTimeZone('UTC'));
-        $datetime->setTime(0, 0, 0);
-
-        $formatter = $this->getDateFormatter('en', $datetype, $timetype, 'UTC');
-
-        $formatted = $formatter->format($datetime);
-
-        // Ignore differences that vary by version of PHP or ICU
-        $formatted = str_replace(' at ', ', ', $formatted);
-        $formatted = str_replace("\u{202F}", ' ', $formatted);
-
-        $this->assertSame($expected, $formatted);
-    }
-
-    public static function relativeDateTypeProvider()
-    {
-        return [
-            [0, IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'Thursday, January 1, 1970'],
-            [0, IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::NONE, 'January 1, 1970'],
-            [0, IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::NONE, 'Jan 1, 1970'],
-            [0, IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::NONE, '1/1/70'],
-
-            [time(), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'today'],
-            [time(), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'today, 12:00:00 AM Coordinated Universal Time'],
-            [time(), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'today, 12:00:00 AM UTC'],
-            [time(), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'today, 12:00 AM'],
-
-            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'yesterday'],
-            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'yesterday, 12:00:00 AM Coordinated Universal Time'],
-            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'yesterday, 12:00:00 AM UTC'],
-            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'yesterday, 12:00 AM'],
-
-            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'tomorrow'],
-            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'tomorrow, 12:00:00 AM Coordinated Universal Time'],
-            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'tomorrow, 12:00:00 AM UTC'],
-            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'tomorrow, 12:00 AM'],
-        ];
-    }
-
-    public function testGetCalendar()
-    {
-        $formatter = $this->getDefaultDateFormatter();
-        $this->assertEquals(IntlDateFormatter::GREGORIAN, $formatter->getCalendar());
-    }
-
-    public function testGetDateType()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::FULL, IntlDateFormatter::NONE);
-        $this->assertEquals(IntlDateFormatter::FULL, $formatter->getDateType());
-    }
-
-    public function testGetLocale()
-    {
-        $formatter = $this->getDefaultDateFormatter();
-        $this->assertEquals('en', $formatter->getLocale());
-    }
-
-    public function testGetPattern()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::FULL, IntlDateFormatter::NONE, 'UTC', IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd');
-        $this->assertEquals('yyyy-MM-dd', $formatter->getPattern());
-    }
-
-    public function testGetTimeType()
-    {
-        $formatter = $this->getDateFormatter('en', IntlDateFormatter::NONE, IntlDateFormatter::FULL);
-        $this->assertEquals(IntlDateFormatter::FULL, $formatter->getTimeType());
-    }
-
-    /**
-     * @dataProvider parseProvider
-     */
-    public function testParse($pattern, $value, $expected)
-    {
-        $errorCode = Icu::U_ZERO_ERROR;
-        $errorMessage = 'U_ZERO_ERROR';
-
-        $formatter = $this->getDefaultDateFormatter($pattern);
-        $this->assertSame($expected, $formatter->parse($value));
-        $this->assertIsIntlSuccess($formatter, $errorMessage, $errorCode);
-    }
-
-    public static function parseProvider()
-    {
-        return array_merge(
-            static::parseYearProvider(),
-            static::parseQuarterProvider(),
-            static::parseMonthProvider(),
-            static::parseStandaloneMonthProvider(),
-            static::parseDayProvider(),
-            static::parseDayOfWeekProvider(),
-            static::parseDayOfYearProvider(),
-            static::parseHour12ClockOneBasedProvider(),
-            static::parseHour12ClockZeroBasedProvider(),
-            static::parseHour24ClockOneBasedProvider(),
-            static::parseHour24ClockZeroBasedProvider(),
-            static::parseMinuteProvider(),
-            static::parseSecondProvider(),
-            static::parseTimezoneProvider(),
-            static::parseAmPmProvider(),
-            static::parseStandaloneAmPmProvider(),
-            static::parseRegexMetaCharsProvider(),
-            static::parseQuoteCharsProvider(),
-            static::parseDashSlashProvider()
-        );
-    }
-
-    public static function parseYearProvider()
-    {
-        return [
-            ['y-M-d', '1970-1-1', 0],
-            ['yy-M-d', '70-1-1', 0],
-        ];
-    }
-
-    public static function parseQuarterProvider()
-    {
-        return [
-            ['Q', '1', 0],
-            ['QQ', '01', 0],
-            ['QQQ', 'Q1', 0],
-            ['QQQQ', '1st quarter', 0],
-            ['QQQQQ', '1st quarter', 0],
-
-            ['Q', '2', 7776000],
-            ['QQ', '02', 7776000],
-            ['QQQ', 'Q2', 7776000],
-            ['QQQQ', '2nd quarter', 7776000],
-            ['QQQQQ', '2nd quarter', 7776000],
-
-            ['q', '1', 0],
-            ['qq', '01', 0],
-            ['qqq', 'Q1', 0],
-            ['qqqq', '1st quarter', 0],
-            ['qqqqq', '1st quarter', 0],
-        ];
-    }
-
-    public static function parseMonthProvider()
-    {
-        return [
-            ['y-M-d', '1970-1-1', 0],
-            ['y-MM-d', '1970-1-1', 0],
-            ['y-MMM-d', '1970-Jan-1', 0],
-            ['y-MMMM-d', '1970-January-1', 0],
-        ];
-    }
-
-    public static function parseStandaloneMonthProvider()
-    {
-        return [
-            ['y-L-d', '1970-1-1', 0],
-            ['y-LLL-d', '1970-Jan-1', 0],
-            ['y-LLLL-d', '1970-January-1', 0],
-        ];
-    }
-
-    public static function parseDayProvider()
-    {
-        return [
-            ['y-M-d', '1970-1-1', 0],
-            ['y-M-dd', '1970-1-1', 0],
-            ['y-M-dd', '1970-1-01', 0],
-            ['y-M-ddd', '1970-1-001', 0],
-        ];
-    }
-
-    public static function parseDayOfWeekProvider()
-    {
-        return [
-            ['E', 'Thu', 0],
-            ['EE', 'Thu', 0],
-            ['EEE', 'Thu', 0],
-            ['EEEE', 'Thursday', 0],
-            ['EEEEE', 'T', 432000],
-            ['EEEEEE', 'Th', 0],
-        ];
-    }
-
-    public static function parseDayOfYearProvider()
-    {
-        return [
-            ['D', '1', 0],
-            ['D', '2', 86400],
-        ];
-    }
-
-    public static function parseHour12ClockOneBasedProvider()
-    {
-        return [
-            // 12 hours (1-12)
-            ['y-M-d h', '1970-1-1 1', 3600],
-            ['y-M-d h', '1970-1-1 10', 36000],
-            ['y-M-d hh', '1970-1-1 11', 39600],
-            ['y-M-d hh', '1970-1-1 12', 0],
-            ['y-M-d hh a', '1970-1-1 0 AM', 0],
-            ['y-M-d hh a', '1970-1-1 1 AM', 3600],
-            ['y-M-d hh a', '1970-1-1 10 AM', 36000],
-            ['y-M-d hh a', '1970-1-1 11 AM', 39600],
-            ['y-M-d hh a', '1970-1-1 12 AM', 0],
-            ['y-M-d hh a', '1970-1-1 23 AM', 82800],
-            ['y-M-d hh a', '1970-1-1 24 AM', 86400],
-            ['y-M-d hh a', '1970-1-1 0 PM', 43200],
-            ['y-M-d hh a', '1970-1-1 1 PM', 46800],
-            ['y-M-d hh a', '1970-1-1 10 PM', 79200],
-            ['y-M-d hh a', '1970-1-1 11 PM', 82800],
-            ['y-M-d hh a', '1970-1-1 12 PM', 43200],
-            ['y-M-d hh a', '1970-1-1 23 PM', 126000],
-            ['y-M-d hh a', '1970-1-1 24 PM', 129600],
-        ];
-    }
-
-    public static function parseHour12ClockZeroBasedProvider()
-    {
-        return [
-            // 12 hours (0-11)
-            ['y-M-d K', '1970-1-1 1', 3600],
-            ['y-M-d K', '1970-1-1 10', 36000],
-            ['y-M-d KK', '1970-1-1 11', 39600],
-            ['y-M-d KK', '1970-1-1 12', 43200],
-            ['y-M-d KK a', '1970-1-1 0 AM', 0],
-            ['y-M-d KK a', '1970-1-1 1 AM', 3600],
-            ['y-M-d KK a', '1970-1-1 10 AM', 36000],
-            ['y-M-d KK a', '1970-1-1 11 AM', 39600],
-            ['y-M-d KK a', '1970-1-1 12 AM', 43200],
-            ['y-M-d KK a', '1970-1-1 23 AM', 82800],
-            ['y-M-d KK a', '1970-1-1 24 AM', 86400],
-            ['y-M-d KK a', '1970-1-1 0 PM', 43200],
-            ['y-M-d KK a', '1970-1-1 1 PM', 46800],
-            ['y-M-d KK a', '1970-1-1 10 PM', 79200],
-            ['y-M-d KK a', '1970-1-1 11 PM', 82800],
-            ['y-M-d KK a', '1970-1-1 12 PM', 86400],
-            ['y-M-d KK a', '1970-1-1 23 PM', 126000],
-            ['y-M-d KK a', '1970-1-1 24 PM', 129600],
-        ];
-    }
-
-    public static function parseHour24ClockOneBasedProvider()
-    {
-        return [
-            // 24 hours (1-24)
-            ['y-M-d k', '1970-1-1 1', 3600],
-            ['y-M-d k', '1970-1-1 10', 36000],
-            ['y-M-d kk', '1970-1-1 11', 39600],
-            ['y-M-d kk', '1970-1-1 12', 43200],
-            ['y-M-d kk', '1970-1-1 23', 82800],
-            ['y-M-d kk', '1970-1-1 24', 0],
-            ['y-M-d kk a', '1970-1-1 0 AM', 0],
-            ['y-M-d kk a', '1970-1-1 1 AM', 0],
-            ['y-M-d kk a', '1970-1-1 10 AM', 0],
-            ['y-M-d kk a', '1970-1-1 11 AM', 0],
-            ['y-M-d kk a', '1970-1-1 12 AM', 0],
-            ['y-M-d kk a', '1970-1-1 23 AM', 0],
-            ['y-M-d kk a', '1970-1-1 24 AM', 0],
-            ['y-M-d kk a', '1970-1-1 0 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 1 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 10 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 11 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 12 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 23 PM', 43200],
-            ['y-M-d kk a', '1970-1-1 24 PM', 43200],
-        ];
-    }
-
-    public static function parseHour24ClockZeroBasedProvider()
-    {
-        return [
-            // 24 hours (0-23)
-            ['y-M-d H', '1970-1-1 0', 0],
-            ['y-M-d H', '1970-1-1 1', 3600],
-            ['y-M-d H', '1970-1-1 10', 36000],
-            ['y-M-d HH', '1970-1-1 11', 39600],
-            ['y-M-d HH', '1970-1-1 12', 43200],
-            ['y-M-d HH', '1970-1-1 23', 82800],
-            ['y-M-d HH a', '1970-1-1 0 AM', 0],
-            ['y-M-d HH a', '1970-1-1 1 AM', 0],
-            ['y-M-d HH a', '1970-1-1 10 AM', 0],
-            ['y-M-d HH a', '1970-1-1 11 AM', 0],
-            ['y-M-d HH a', '1970-1-1 12 AM', 0],
-            ['y-M-d HH a', '1970-1-1 23 AM', 0],
-            ['y-M-d HH a', '1970-1-1 24 AM', 0],
-            ['y-M-d HH a', '1970-1-1 0 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 1 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 10 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 11 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 12 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 23 PM', 43200],
-            ['y-M-d HH a', '1970-1-1 24 PM', 43200],
-        ];
-    }
-
-    public static function parseMinuteProvider()
-    {
-        return [
-            ['y-M-d HH:m', '1970-1-1 0:1', 60],
-            ['y-M-d HH:mm', '1970-1-1 0:10', 600],
-        ];
-    }
-
-    public static function parseSecondProvider()
-    {
-        return [
-            ['y-M-d HH:mm:s', '1970-1-1 00:01:1', 61],
-            ['y-M-d HH:mm:ss', '1970-1-1 00:01:10', 70],
-        ];
-    }
-
-    public static function parseTimezoneProvider()
-    {
-        return [
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-03:00', 10800],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-04:00', 14400],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-00:00', 0],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+03:00', -10800],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+04:00', -14400],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-0300', 10800],
-            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+0300', -10800],
-
-            // a previous timezone parsing should not change the timezone for the next parsing
-            ['y-M-d HH:mm:ss', '1970-1-1 00:00:00', 0],
-        ];
-    }
-
-    public static function parseAmPmProvider()
-    {
-        return [
-            // AM/PM (already covered by hours tests)
-            ['y-M-d HH:mm:ss a', '1970-1-1 00:00:00 AM', 0],
-            ['y-M-d HH:mm:ss a', '1970-1-1 00:00:00 PM', 43200],
-        ];
-    }
-
-    public static function parseStandaloneAmPmProvider()
-    {
-        return [
-            ['a', 'AM', 0],
-            ['a', 'PM', 43200],
-        ];
-    }
-
-    public static function parseRegexMetaCharsProvider()
-    {
-        return [
-            // regexp meta chars in the pattern string
-            ['y[M-d', '1970[1-1', 0],
-            ['y[M/d', '1970[1/1', 0],
-        ];
-    }
-
-    public static function parseQuoteCharsProvider()
-    {
-        return [
-            ["'M'", 'M', 0],
-            ["'yy'", 'yy', 0],
-            ["'''yy'", "'yy", 0],
-            ["''y", "'1970", 0],
-            ["H 'o'' clock'", "0 o' clock", 0],
-        ];
-    }
-
-    public static function parseDashSlashProvider()
-    {
-        return [
-            ['y-M-d', '1970/1/1', 0],
-            ['yy-M-d', '70/1/1', 0],
-            ['y/M/d', '1970-1-1', 0],
-            ['yy/M/d', '70-1-1', 0],
-        ];
-    }
-
-    /**
-     * @dataProvider parseErrorProvider
-     */
-    public function testParseError($pattern, $value)
-    {
-        $errorCode = Icu::U_PARSE_ERROR;
-        $errorMessage = 'Date parsing failed: U_PARSE_ERROR';
-
-        $formatter = $this->getDefaultDateFormatter($pattern);
-        $this->assertFalse($formatter->parse($value));
-        $this->assertIsIntlFailure($formatter, $errorMessage, $errorCode);
-    }
-
-    public static function parseErrorProvider()
-    {
-        return [
-            // 1 char month
-            ['y-MMMMM-d', '1970-J-1'],
-            ['y-MMMMM-d', '1970-S-1'],
-
-            // standalone 1 char month
-            ['y-LLLLL-d', '1970-J-1'],
-            ['y-LLLLL-d', '1970-S-1'],
-        ];
-    }
-
-    /*
-     * https://github.com/symfony/symfony/issues/4242
-     */
-    public function testParseAfterError()
-    {
-        $this->testParseError('y-MMMMM-d', '1970-J-1');
-        $this->testParse('y-M-d', '1970-1-1', 0);
-    }
-
-    public function testParseWithNullPositionValue()
-    {
-        $position = null;
-        $formatter = $this->getDefaultDateFormatter('y');
-        $this->assertSame(0, $formatter->parse('1970', $position));
-        // Since $position is not supported by the Symfony implementation, the following won't work.
-        // The intl implementation works this way since 60.2.
-        // $this->assertSame(4, $position);
-    }
-
-    public function testSetPattern()
-    {
-        $formatter = $this->getDefaultDateFormatter();
-        $formatter->setPattern('yyyy-MM-dd');
-        $this->assertEquals('yyyy-MM-dd', $formatter->getPattern());
-    }
-
-    /**
-     * @dataProvider setTimeZoneProvider
-     */
-    public function testSetTimeZone($timeZoneId, $expectedTimeZoneId)
-    {
-        $formatter = $this->getDefaultDateFormatter();
-
-        $formatter->setTimeZone($timeZoneId);
-
-        $this->assertEquals($expectedTimeZoneId, $formatter->getTimeZoneId());
-    }
-
-    public static function setTimeZoneProvider()
-    {
-        return [
-            ['UTC', 'UTC'],
-            ['GMT', 'GMT'],
-            ['GMT-03:00', 'GMT-03:00'],
-            ['Europe/Zurich', 'Europe/Zurich'],
-            [null, date_default_timezone_get()],
-            ['Foo/Bar', 'UTC'],
-            ['GMT+00:AA', 'UTC'],
-            ['GMT+00AA', 'UTC'],
-        ];
-    }
-
-    protected function getDefaultDateFormatter($pattern = null)
-    {
-        return $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'UTC', IntlDateFormatter::GREGORIAN, $pattern);
-    }
-
-    protected function getDateTime($timestamp, $timeZone)
-    {
-        $dateTime = new \DateTime();
-        $dateTime->setTimestamp(null === $timestamp ? time() : $timestamp);
-        $dateTime->setTimezone(new \DateTimeZone($timeZone ?: getenv('TZ') ?: 'UTC'));
-
-        return $dateTime;
-    }
-
-    protected function assertIsIntlFailure($formatter, $errorMessage, $errorCode)
-    {
-        $this->assertSame($errorMessage, $this->getIntlErrorMessage());
-        $this->assertSame($errorCode, $this->getIntlErrorCode());
-        $this->assertTrue($this->isIntlFailure($this->getIntlErrorCode()));
-        $this->assertSame($errorMessage, $formatter->getErrorMessage());
-        $this->assertSame($errorCode, $formatter->getErrorCode());
-        $this->assertTrue($this->isIntlFailure($formatter->getErrorCode()));
-    }
-
-    protected function assertIsIntlSuccess($formatter, $errorMessage, $errorCode)
-    {
-        /* @var IntlDateFormatter $formatter */
-        $this->assertSame($errorMessage, $this->getIntlErrorMessage());
-        $this->assertSame($errorCode, $this->getIntlErrorCode());
-        $this->assertFalse($this->isIntlFailure($this->getIntlErrorCode()));
-        $this->assertSame($errorMessage, $formatter->getErrorMessage());
-        $this->assertSame($errorCode, $formatter->getErrorCode());
-        $this->assertFalse($this->isIntlFailure($formatter->getErrorCode()));
-    }
-
-    /**
-     * @return IntlDateFormatter|\IntlDateFormatter
-     */
-    abstract protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null);
-
-    abstract protected function getIntlErrorMessage(): string;
-
-    abstract protected function getIntlErrorCode(): int;
-
-    /**
-     * @param int $errorCode
-     */
-    abstract protected function isIntlFailure($errorCode): bool;
-}
diff --git a/tests/Intl/Icu/AbstractIntlDateFormatterTestCase.php b/tests/Intl/Icu/AbstractIntlDateFormatterTestCase.php
new file mode 100644
index 0000000..bf3249b
--- /dev/null
+++ b/tests/Intl/Icu/AbstractIntlDateFormatterTestCase.php
@@ -0,0 +1,1032 @@
+<?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\Polyfill\Tests\Intl\Icu;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Polyfill\Intl\Icu\Icu;
+use Symfony\Polyfill\Intl\Icu\Intl;
+use Symfony\Polyfill\Intl\Icu\IntlDateFormatter;
+
+/**
+ * Test case for IntlDateFormatter implementations.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+abstract class AbstractIntlDateFormatterTestCase extends TestCase
+{
+    protected function setUp(): void
+    {
+        \Locale::setDefault('en');
+    }
+
+    /**
+     * When a time zone is not specified, it uses the system default however it returns null in the getter method.
+     */
+    public function testConstructorDefaultTimeZone()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
+
+        $this->assertSame(date_default_timezone_get(), $formatter->getTimeZoneId());
+
+        $this->assertSame(
+            $this->getDateTime(0, $formatter->getTimeZoneId())->format('M j, Y, g:i A'),
+            str_replace("\u{202F}", ' ', $formatter->format(0))
+        );
+    }
+
+    /**
+     * @group legacy
+     */
+    public function testConstructorWithoutDateType()
+    {
+        $formatter = $this->getDateFormatter('en', null, IntlDateFormatter::SHORT, 'UTC', IntlDateFormatter::GREGORIAN);
+
+        $this->assertSame('EEEE, MMMM d, y \'at\' h:mm a', str_replace("\u{202F}", ' ', $formatter->getPattern()));
+    }
+
+    /**
+     * @group legacy
+     */
+    public function testConstructorWithoutTimeType()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::SHORT, null, 'UTC', IntlDateFormatter::GREGORIAN);
+
+        $this->assertSame('M/d/yy, h:mm:ss a zzzz', str_replace("\u{202F}", ' ', $formatter->getPattern()));
+    }
+
+    /**
+     * @dataProvider formatProvider
+     */
+    public function testFormat($pattern, $timestamp, $expected)
+    {
+        $errorCode = Icu::U_ZERO_ERROR;
+        $errorMessage = 'U_ZERO_ERROR';
+
+        $formatter = $this->getDefaultDateFormatter($pattern);
+        $this->assertSame($expected, $formatter->format($timestamp));
+        $this->assertIsIntlSuccess($formatter, $errorMessage, $errorCode);
+    }
+
+    public static function formatProvider()
+    {
+        $dateTime = new \DateTime('@0');
+        $dateTimeImmutable = new \DateTimeImmutable('@0');
+
+        /* https://unicode-org.atlassian.net/browse/ICU-21647 */
+        $expectedQuarterX5 = '1';
+        if (\defined('INTL_ICU_VERSION') && version_compare(\INTL_ICU_VERSION, '70.1', '<')) {
+            $expectedQuarterX5 = '1st quarter';
+        }
+
+        $formatData = [
+            /* general */
+            ['y-M-d', 0, '1970-1-1'],
+            ["EEE, MMM d, ''yy", 0, "Thu, Jan 1, '70"],
+            ['h:mm a', 0, '12:00 AM'],
+            ['yyyyy.MMMM.dd hh:mm aaa', 0, '01970.January.01 12:00 AM'],
+
+            /* escaping */
+            ["'M'", 0, 'M'],
+            ["'yy'", 0, 'yy'],
+            ["'''yy'", 0, "'yy"],
+            ["''y", 0, "'1970"],
+            ["''yy", 0, "'70"],
+            ["H 'o'' clock'", 0, "0 o' clock"],
+
+            /* month */
+            ['M', 0, '1'],
+            ['MM', 0, '01'],
+            ['MMM', 0, 'Jan'],
+            ['MMMM', 0, 'January'],
+            ['MMMMM', 0, 'J'],
+            ['MMMMMM', 0, '000001'],
+
+            ['L', 0, '1'],
+            ['LL', 0, '01'],
+            ['LLL', 0, 'Jan'],
+            ['LLLL', 0, 'January'],
+            ['LLLLL', 0, 'J'],
+            ['LLLLLL', 0, '000001'],
+
+            /* year */
+            ['y', 0, '1970'],
+            ['yy', 0, '70'],
+            ['yyy', 0, '1970'],
+            ['yyyy', 0, '1970'],
+            ['yyyyy', 0, '01970'],
+            ['yyyyyy', 0, '001970'],
+
+            /* day */
+            ['d', 0, '1'],
+            ['dd', 0, '01'],
+            ['ddd', 0, '001'],
+
+            /* quarter */
+            ['Q', 0, '1'],
+            ['QQ', 0, '01'],
+            ['QQQ', 0, 'Q1'],
+            ['QQQQ', 0, '1st quarter'],
+            ['QQQQQ', 0, $expectedQuarterX5],
+
+            ['q', 0, '1'],
+            ['qq', 0, '01'],
+            ['qqq', 0, 'Q1'],
+            ['qqqq', 0, '1st quarter'],
+            ['qqqqq', 0, $expectedQuarterX5],
+
+            // 4 months
+            ['Q', 7776000, '2'],
+            ['QQ', 7776000, '02'],
+            ['QQQ', 7776000, 'Q2'],
+            ['QQQQ', 7776000, '2nd quarter'],
+
+            // 7 months
+            ['QQQQ', 15638400, '3rd quarter'],
+
+            // 10 months
+            ['QQQQ', 23587200, '4th quarter'],
+
+            /* 12-hour (1-12) */
+            ['h', 0, '12'],
+            ['hh', 0, '12'],
+            ['hhh', 0, '012'],
+
+            ['h', 1, '12'],
+            ['h', 3600, '1'],
+            ['h', 43200, '12'], // 12 hours
+
+            /* day of year */
+            ['D', 0, '1'],
+            ['D', 86400, '2'], // 1 day
+            ['D', 31536000, '1'], // 1 year
+            ['D', 31622400, '2'], // 1 year + 1 day
+
+            /* day of week */
+            ['E', 0, 'Thu'],
+            ['EE', 0, 'Thu'],
+            ['EEE', 0, 'Thu'],
+            ['EEEE', 0, 'Thursday'],
+            ['EEEEE', 0, 'T'],
+            ['EEEEEE', 0, 'Th'],
+
+            ['E', 1296540000, 'Tue'], // 2011-02-01
+            ['E', 1296950400, 'Sun'], // 2011-02-06
+
+            /* am/pm marker */
+            ['a', 0, 'AM'],
+            ['aa', 0, 'AM'],
+            ['aaa', 0, 'AM'],
+            ['aaaa', 0, 'AM'],
+
+            // 12 hours
+            ['a', 43200, 'PM'],
+            ['aa', 43200, 'PM'],
+            ['aaa', 43200, 'PM'],
+            ['aaaa', 43200, 'PM'],
+
+            /* 24-hour (0-23) */
+            ['H', 0, '0'],
+            ['HH', 0, '00'],
+            ['HHH', 0, '000'],
+
+            ['H', 1, '0'],
+            ['H', 3600, '1'],
+            ['H', 43200, '12'],
+            ['H', 46800, '13'],
+
+            /* 24-hour (1-24) */
+            ['k', 0, '24'],
+            ['kk', 0, '24'],
+            ['kkk', 0, '024'],
+
+            ['k', 1, '24'],
+            ['k', 3600, '1'],
+            ['k', 43200, '12'],
+            ['k', 46800, '13'],
+
+            /* 12-hour (0-11) */
+            ['K', 0, '0'],
+            ['KK', 0, '00'],
+            ['KKK', 0, '000'],
+
+            ['K', 1, '0'],
+            ['K', 3600, '1'],
+            ['K', 43200, '0'], // 12 hours
+
+            /* minute */
+            ['m', 0, '0'],
+            ['mm', 0, '00'],
+            ['mmm', 0, '000'],
+
+            ['m', 1, '0'],
+            ['m', 60, '1'],
+            ['m', 120, '2'],
+            ['m', 180, '3'],
+            ['m', 3600, '0'],
+            ['m', 3660, '1'],
+            ['m', 43200, '0'], // 12 hours
+
+            /* second */
+            ['s', 0, '0'],
+            ['ss', 0, '00'],
+            ['sss', 0, '000'],
+
+            ['s', 1, '1'],
+            ['s', 2, '2'],
+            ['s', 5, '5'],
+            ['s', 30, '30'],
+            ['s', 59, '59'],
+            ['s', 60, '0'],
+            ['s', 120, '0'],
+            ['s', 180, '0'],
+            ['s', 3600, '0'],
+            ['s', 3601, '1'],
+            ['s', 3630, '30'],
+            ['s', 43200, '0'], // 12 hours
+        ];
+
+        /* general, DateTime */
+        $formatData[] = ['y-M-d', $dateTime, '1970-1-1'];
+        $formatData[] = ["EEE, MMM d, ''yy", $dateTime, "Thu, Jan 1, '70"];
+        $formatData[] = ['h:mm a', $dateTime, '12:00 AM'];
+        $formatData[] = ['yyyyy.MMMM.dd hh:mm aaa', $dateTime, '01970.January.01 12:00 AM'];
+
+        /* general, DateTimeImmutable */
+        $formatData[] = ['y-M-d', $dateTimeImmutable, '1970-1-1'];
+        $formatData[] = ["EEE, MMM d, ''yy", $dateTimeImmutable, "Thu, Jan 1, '70"];
+        $formatData[] = ['h:mm a', $dateTimeImmutable, '12:00 AM'];
+        $formatData[] = ['yyyyy.MMMM.dd hh:mm aaa', $dateTimeImmutable, '01970.January.01 12:00 AM'];
+
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '59.1', '>=')) {
+            // Before ICU 59.1 GMT was used instead of UTC
+            $formatData[] = ["yyyy.MM.dd 'at' HH:mm:ss zzz", 0, '1970.01.01 at 00:00:00 UTC'];
+            $formatData[] = ['K:mm a, z', 0, '0:00 AM, UTC'];
+            $formatData[] = ["yyyy.MM.dd 'at' HH:mm:ss zzz", $dateTime, '1970.01.01 at 00:00:00 UTC'];
+            $formatData[] = ['K:mm a, z', $dateTime, '0:00 AM, UTC'];
+        }
+
+        return $formatData;
+    }
+
+    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')));
+        $this->assertSame('1970.01.01 at 00:00:00 GMT', $gmtFormatter->format(new \DateTimeImmutable('@0')));
+        $this->assertSame('1970.01.01 at 00:00:00 UTC', $utcFormatter->format(new \DateTimeImmutable('@0')));
+    }
+
+    /**
+     * @dataProvider formatErrorProvider
+     */
+    public function testFormatIllegalArgumentError($pattern, $timestamp, $errorMessage)
+    {
+        $errorCode = Icu::U_ILLEGAL_ARGUMENT_ERROR;
+
+        $formatter = $this->getDefaultDateFormatter($pattern);
+        $this->assertFalse($formatter->format($timestamp));
+        $this->assertIsIntlFailure($formatter, $errorMessage, $errorCode);
+    }
+
+    public static function formatErrorProvider()
+    {
+        return [
+            ['y-M-d', 'foobar', 'datefmt_format: string \'foobar\' is not numeric, which would be required for it to be a valid date: U_ILLEGAL_ARGUMENT_ERROR'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatWithTimezoneProvider
+     */
+    public function testFormatWithTimezone($timestamp, $timezone, $expected)
+    {
+        $pattern = 'yyyy-MM-dd HH:mm:ss';
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, $timezone, IntlDateFormatter::GREGORIAN, $pattern);
+        $this->assertSame($expected, $formatter->format($timestamp));
+    }
+
+    public static function formatWithTimezoneProvider()
+    {
+        $data = [
+            [0, 'UTC', '1970-01-01 00:00:00'],
+            [0, 'GMT', '1970-01-01 00:00:00'],
+            [0, 'GMT-03:00', '1969-12-31 21:00:00'],
+            [0, 'GMT+03:00', '1970-01-01 03:00:00'],
+            [0, 'Europe/Zurich', '1970-01-01 01:00:00'],
+            [0, 'Europe/Paris', '1970-01-01 01:00:00'],
+            [0, 'Africa/Cairo', '1970-01-01 02:00:00'],
+            [0, 'Africa/Casablanca', '1970-01-01 00:00:00'],
+            [0, 'Africa/Djibouti', '1970-01-01 03:00:00'],
+            [0, 'Africa/Johannesburg', '1970-01-01 02:00:00'],
+            [0, 'America/Antigua', '1969-12-31 20:00:00'],
+            [0, 'America/Toronto', '1969-12-31 19:00:00'],
+            [0, 'America/Vancouver', '1969-12-31 16:00:00'],
+            [0, 'Asia/Aqtau', '1970-01-01 05:00:00'],
+            [0, 'Asia/Bangkok', '1970-01-01 07:00:00'],
+            [0, 'Asia/Dubai', '1970-01-01 04:00:00'],
+            [0, 'Australia/Brisbane', '1970-01-01 10:00:00'],
+            [0, 'Australia/Eucla', '1970-01-01 08:45:00'],
+            [0, 'Australia/Melbourne', '1970-01-01 10:00:00'],
+            [0, 'Europe/Berlin', '1970-01-01 01:00:00'],
+            [0, 'Europe/Dublin', '1970-01-01 01:00:00'],
+            [0, 'Europe/Warsaw', '1970-01-01 01:00:00'],
+            [0, 'Pacific/Fiji', '1970-01-01 12:00:00'],
+        ];
+
+        return $data;
+    }
+
+    /**
+     * @dataProvider formatTimezoneProvider
+     */
+    public function testFormatTimezone($pattern, $timezone, $expected)
+    {
+        $formatter = $this->getDefaultDateFormatter($pattern);
+        $formatter->setTimeZone(new \DateTimeZone($timezone));
+
+        $this->assertEquals($expected, $formatter->format(0));
+    }
+
+    public static function formatTimezoneProvider()
+    {
+        return [
+            ['z', 'GMT', 'GMT'],
+            ['zz', 'GMT', 'GMT'],
+            ['zzz', 'GMT', 'GMT'],
+            ['zzzz', 'GMT', 'Greenwich Mean Time'],
+            ['zzzzz', 'GMT', 'Greenwich Mean Time'],
+
+            ['z', 'Etc/GMT', 'GMT'],
+            ['zz', 'Etc/GMT', 'GMT'],
+            ['zzz', 'Etc/GMT', 'GMT'],
+            ['zzzz', 'Etc/GMT', 'Greenwich Mean Time'],
+            ['zzzzz', 'Etc/GMT', 'Greenwich Mean Time'],
+
+            ['z', 'Etc/GMT+3', 'GMT-3'],
+            ['zz', 'Etc/GMT+3', 'GMT-3'],
+            ['zzz', 'Etc/GMT+3', 'GMT-3'],
+            ['zzzz', 'Etc/GMT+3', 'GMT-03:00'],
+            ['zzzzz', 'Etc/GMT+3', 'GMT-03:00'],
+
+            ['z', 'UTC', 'UTC'],
+            ['zz', 'UTC', 'UTC'],
+            ['zzz', 'UTC', 'UTC'],
+            ['zzzz', 'UTC', 'Coordinated Universal Time'],
+            ['zzzzz', 'UTC', 'Coordinated Universal Time'],
+
+            ['z', 'Etc/UTC', 'UTC'],
+            ['zz', 'Etc/UTC', 'UTC'],
+            ['zzz', 'Etc/UTC', 'UTC'],
+            ['zzzz', 'Etc/UTC', 'Coordinated Universal Time'],
+            ['zzzzz', 'Etc/UTC', 'Coordinated Universal Time'],
+
+            ['z', 'Etc/Universal', 'UTC'],
+            ['z', 'Etc/Zulu', 'UTC'],
+            ['z', 'Etc/UCT', 'UTC'],
+            ['z', 'Etc/Greenwich', 'GMT'],
+            ['zzzzz', 'Etc/Universal', 'Coordinated Universal Time'],
+            ['zzzzz', 'Etc/Zulu', 'Coordinated Universal Time'],
+            ['zzzzz', 'Etc/UCT', 'Coordinated Universal Time'],
+            ['zzzzz', 'Etc/Greenwich', 'Greenwich Mean Time'],
+
+            ['z', 'GMT+03:00', 'GMT+3'],
+            ['zz', 'GMT+03:00', 'GMT+3'],
+            ['zzz', 'GMT+03:00', 'GMT+3'],
+            ['zzzz', 'GMT+03:00', 'GMT+03:00'],
+            ['zzzzz', 'GMT+03:00', 'GMT+03:00'],
+        ];
+    }
+
+    public function testFormatWithGmtTimezone()
+    {
+        $formatter = $this->getDefaultDateFormatter('zzzz');
+
+        $formatter->setTimeZone('GMT+03:00');
+
+        $this->assertEquals('GMT+03:00', $formatter->format(0));
+    }
+
+    public function testFormatWithGmtTimeZoneAndMinutesOffset()
+    {
+        $formatter = $this->getDefaultDateFormatter('zzzz');
+
+        $formatter->setTimeZone('GMT+00:30');
+
+        $this->assertEquals('GMT+00:30', $formatter->format(0));
+    }
+
+    public function testFormatWithNonStandardTimezone()
+    {
+        $formatter = $this->getDefaultDateFormatter('zzzz');
+
+        $formatter->setTimeZone('Pacific/Fiji');
+
+        $this->assertEquals('Fiji Standard Time', $formatter->format(0));
+    }
+
+    public function testFormatWithConstructorTimezone()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'UTC');
+        $formatter->setPattern('yyyy-MM-dd HH:mm:ss');
+
+        $this->assertEquals(
+            $this->getDateTime(0, 'UTC')->format('Y-m-d H:i:s'),
+            $formatter->format(0)
+        );
+    }
+
+    public function testFormatWithDateTimeZoneGmt()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzz');
+
+        $this->assertEquals('GMT', $formatter->format(0));
+    }
+
+    public function testFormatWithDateTimeZoneGmtOffset()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, new \DateTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
+
+        $this->assertEquals('GMT+03:00', $formatter->format(0));
+    }
+
+    /**
+     * @requires extension intl
+     */
+    public function testFormatWithIntlTimeZone()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, \IntlTimeZone::createTimeZone('GMT+03:00'), IntlDateFormatter::GREGORIAN, 'zzzz');
+
+        $this->assertEquals('GMT+03:00', $formatter->format(0));
+    }
+
+    public function testFormatWithTimezoneFromPhp()
+    {
+        $tz = date_default_timezone_get();
+        date_default_timezone_set('Europe/London');
+
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
+        $formatter->setPattern('yyyy-MM-dd HH:mm:ss');
+
+        $this->assertEquals(
+            $this->getDateTime(0, 'Europe/London')->format('Y-m-d H:i:s'),
+            $formatter->format(0)
+        );
+
+        $this->assertEquals('Europe/London', date_default_timezone_get());
+
+        // Restores TZ.
+        date_default_timezone_set($tz);
+    }
+
+    public function testFormatIgnoresPatternForRelativeDateType()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::FULL, new \DateTimeZone('GMT'), IntlDateFormatter::GREGORIAN, 'zzzz');
+
+        $datetime = \DateTime::createFromFormat('U', time(), new \DateTimeZone('GMT'));
+        $datetime->setTime(0, 0, 0);
+
+        $formatted = $formatter->format($datetime);
+        $formatted = str_replace(' at ', ', ', $formatted);
+        $formatted = str_replace("\u{202F}", ' ', $formatted);
+
+        $this->assertSame('today, 12:00:00 AM Greenwich Mean Time', $formatted);
+    }
+
+    /**
+     * @dataProvider dateAndTimeTypeProvider
+     */
+    public function testDateAndTimeType($timestamp, $datetype, $timetype, $expected)
+    {
+        $formatter = $this->getDateFormatter('en', $datetype, $timetype, 'UTC');
+        $this->assertSame($expected, str_replace("\u{202F}", ' ', $formatter->format($timestamp)));
+    }
+
+    public static function dateAndTimeTypeProvider()
+    {
+        return [
+            [0, IntlDateFormatter::FULL, IntlDateFormatter::NONE, 'Thursday, January 1, 1970'],
+            [0, IntlDateFormatter::LONG, IntlDateFormatter::NONE, 'January 1, 1970'],
+            [0, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, 'Jan 1, 1970'],
+            [0, IntlDateFormatter::SHORT, IntlDateFormatter::NONE, '1/1/70'],
+            [0, IntlDateFormatter::NONE, IntlDateFormatter::FULL, '12:00:00 AM Coordinated Universal Time'],
+            [0, IntlDateFormatter::NONE, IntlDateFormatter::LONG, '12:00:00 AM UTC'],
+            [0, IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM, '12:00:00 AM'],
+            [0, IntlDateFormatter::NONE, IntlDateFormatter::SHORT, '12:00 AM'],
+        ];
+    }
+
+    /**
+     * @dataProvider relativeDateTypeProvider
+     */
+    public function testRelativeDateType($timestamp, $datetype, $timetype, $expected)
+    {
+        $datetime = \DateTime::createFromFormat('U', $timestamp, new \DateTimeZone('UTC'));
+        $datetime->setTime(0, 0, 0);
+
+        $formatter = $this->getDateFormatter('en', $datetype, $timetype, 'UTC');
+
+        $formatted = $formatter->format($datetime);
+
+        // Ignore differences that vary by version of PHP or ICU
+        $formatted = str_replace(' at ', ', ', $formatted);
+        $formatted = str_replace("\u{202F}", ' ', $formatted);
+
+        $this->assertSame($expected, $formatted);
+    }
+
+    public static function relativeDateTypeProvider()
+    {
+        return [
+            [0, IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'Thursday, January 1, 1970'],
+            [0, IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::NONE, 'January 1, 1970'],
+            [0, IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::NONE, 'Jan 1, 1970'],
+            [0, IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::NONE, '1/1/70'],
+
+            [time(), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'today'],
+            [time(), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'today, 12:00:00 AM Coordinated Universal Time'],
+            [time(), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'today, 12:00:00 AM UTC'],
+            [time(), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'today, 12:00 AM'],
+
+            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'yesterday'],
+            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'yesterday, 12:00:00 AM Coordinated Universal Time'],
+            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'yesterday, 12:00:00 AM UTC'],
+            [strtotime('-1 day', time()), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'yesterday, 12:00 AM'],
+
+            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::NONE, 'tomorrow'],
+            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_LONG, IntlDateFormatter::FULL, 'tomorrow, 12:00:00 AM Coordinated Universal Time'],
+            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_MEDIUM, IntlDateFormatter::LONG, 'tomorrow, 12:00:00 AM UTC'],
+            [strtotime('+1 day', time()), IntlDateFormatter::RELATIVE_SHORT, IntlDateFormatter::SHORT, 'tomorrow, 12:00 AM'],
+        ];
+    }
+
+    public function testGetCalendar()
+    {
+        $formatter = $this->getDefaultDateFormatter();
+        $this->assertEquals(IntlDateFormatter::GREGORIAN, $formatter->getCalendar());
+    }
+
+    public function testGetDateType()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::FULL, IntlDateFormatter::NONE);
+        $this->assertEquals(IntlDateFormatter::FULL, $formatter->getDateType());
+    }
+
+    public function testGetLocale()
+    {
+        $formatter = $this->getDefaultDateFormatter();
+        $this->assertEquals('en', $formatter->getLocale());
+    }
+
+    public function testGetPattern()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::FULL, IntlDateFormatter::NONE, 'UTC', IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd');
+        $this->assertEquals('yyyy-MM-dd', $formatter->getPattern());
+    }
+
+    public function testGetTimeType()
+    {
+        $formatter = $this->getDateFormatter('en', IntlDateFormatter::NONE, IntlDateFormatter::FULL);
+        $this->assertEquals(IntlDateFormatter::FULL, $formatter->getTimeType());
+    }
+
+    /**
+     * @dataProvider parseProvider
+     */
+    public function testParse($pattern, $value, $expected)
+    {
+        $errorCode = Icu::U_ZERO_ERROR;
+        $errorMessage = 'U_ZERO_ERROR';
+
+        $formatter = $this->getDefaultDateFormatter($pattern);
+        $this->assertSame($expected, $formatter->parse($value));
+        $this->assertIsIntlSuccess($formatter, $errorMessage, $errorCode);
+    }
+
+    public static function parseProvider()
+    {
+        return array_merge(
+            static::parseYearProvider(),
+            static::parseQuarterProvider(),
+            static::parseMonthProvider(),
+            static::parseStandaloneMonthProvider(),
+            static::parseDayProvider(),
+            static::parseDayOfWeekProvider(),
+            static::parseDayOfYearProvider(),
+            static::parseHour12ClockOneBasedProvider(),
+            static::parseHour12ClockZeroBasedProvider(),
+            static::parseHour24ClockOneBasedProvider(),
+            static::parseHour24ClockZeroBasedProvider(),
+            static::parseMinuteProvider(),
+            static::parseSecondProvider(),
+            static::parseTimezoneProvider(),
+            static::parseAmPmProvider(),
+            static::parseStandaloneAmPmProvider(),
+            static::parseRegexMetaCharsProvider(),
+            static::parseQuoteCharsProvider(),
+            static::parseDashSlashProvider()
+        );
+    }
+
+    public static function parseYearProvider()
+    {
+        return [
+            ['y-M-d', '1970-1-1', 0],
+            ['yy-M-d', '70-1-1', 0],
+        ];
+    }
+
+    public static function parseQuarterProvider()
+    {
+        return [
+            ['Q', '1', 0],
+            ['QQ', '01', 0],
+            ['QQQ', 'Q1', 0],
+            ['QQQQ', '1st quarter', 0],
+            ['QQQQQ', '1st quarter', 0],
+
+            ['Q', '2', 7776000],
+            ['QQ', '02', 7776000],
+            ['QQQ', 'Q2', 7776000],
+            ['QQQQ', '2nd quarter', 7776000],
+            ['QQQQQ', '2nd quarter', 7776000],
+
+            ['q', '1', 0],
+            ['qq', '01', 0],
+            ['qqq', 'Q1', 0],
+            ['qqqq', '1st quarter', 0],
+            ['qqqqq', '1st quarter', 0],
+        ];
+    }
+
+    public static function parseMonthProvider()
+    {
+        return [
+            ['y-M-d', '1970-1-1', 0],
+            ['y-MM-d', '1970-1-1', 0],
+            ['y-MMM-d', '1970-Jan-1', 0],
+            ['y-MMMM-d', '1970-January-1', 0],
+        ];
+    }
+
+    public static function parseStandaloneMonthProvider()
+    {
+        return [
+            ['y-L-d', '1970-1-1', 0],
+            ['y-LLL-d', '1970-Jan-1', 0],
+            ['y-LLLL-d', '1970-January-1', 0],
+        ];
+    }
+
+    public static function parseDayProvider()
+    {
+        return [
+            ['y-M-d', '1970-1-1', 0],
+            ['y-M-dd', '1970-1-1', 0],
+            ['y-M-dd', '1970-1-01', 0],
+            ['y-M-ddd', '1970-1-001', 0],
+        ];
+    }
+
+    public static function parseDayOfWeekProvider()
+    {
+        return [
+            ['E', 'Thu', 0],
+            ['EE', 'Thu', 0],
+            ['EEE', 'Thu', 0],
+            ['EEEE', 'Thursday', 0],
+            ['EEEEE', 'T', 432000],
+            ['EEEEEE', 'Th', 0],
+        ];
+    }
+
+    public static function parseDayOfYearProvider()
+    {
+        return [
+            ['D', '1', 0],
+            ['D', '2', 86400],
+        ];
+    }
+
+    public static function parseHour12ClockOneBasedProvider()
+    {
+        return [
+            // 12 hours (1-12)
+            ['y-M-d h', '1970-1-1 1', 3600],
+            ['y-M-d h', '1970-1-1 10', 36000],
+            ['y-M-d hh', '1970-1-1 11', 39600],
+            ['y-M-d hh', '1970-1-1 12', 0],
+            ['y-M-d hh a', '1970-1-1 0 AM', 0],
+            ['y-M-d hh a', '1970-1-1 1 AM', 3600],
+            ['y-M-d hh a', '1970-1-1 10 AM', 36000],
+            ['y-M-d hh a', '1970-1-1 11 AM', 39600],
+            ['y-M-d hh a', '1970-1-1 12 AM', 0],
+            ['y-M-d hh a', '1970-1-1 23 AM', 82800],
+            ['y-M-d hh a', '1970-1-1 24 AM', 86400],
+            ['y-M-d hh a', '1970-1-1 0 PM', 43200],
+            ['y-M-d hh a', '1970-1-1 1 PM', 46800],
+            ['y-M-d hh a', '1970-1-1 10 PM', 79200],
+            ['y-M-d hh a', '1970-1-1 11 PM', 82800],
+            ['y-M-d hh a', '1970-1-1 12 PM', 43200],
+            ['y-M-d hh a', '1970-1-1 23 PM', 126000],
+            ['y-M-d hh a', '1970-1-1 24 PM', 129600],
+        ];
+    }
+
+    public static function parseHour12ClockZeroBasedProvider()
+    {
+        return [
+            // 12 hours (0-11)
+            ['y-M-d K', '1970-1-1 1', 3600],
+            ['y-M-d K', '1970-1-1 10', 36000],
+            ['y-M-d KK', '1970-1-1 11', 39600],
+            ['y-M-d KK', '1970-1-1 12', 43200],
+            ['y-M-d KK a', '1970-1-1 0 AM', 0],
+            ['y-M-d KK a', '1970-1-1 1 AM', 3600],
+            ['y-M-d KK a', '1970-1-1 10 AM', 36000],
+            ['y-M-d KK a', '1970-1-1 11 AM', 39600],
+            ['y-M-d KK a', '1970-1-1 12 AM', 43200],
+            ['y-M-d KK a', '1970-1-1 23 AM', 82800],
+            ['y-M-d KK a', '1970-1-1 24 AM', 86400],
+            ['y-M-d KK a', '1970-1-1 0 PM', 43200],
+            ['y-M-d KK a', '1970-1-1 1 PM', 46800],
+            ['y-M-d KK a', '1970-1-1 10 PM', 79200],
+            ['y-M-d KK a', '1970-1-1 11 PM', 82800],
+            ['y-M-d KK a', '1970-1-1 12 PM', 86400],
+            ['y-M-d KK a', '1970-1-1 23 PM', 126000],
+            ['y-M-d KK a', '1970-1-1 24 PM', 129600],
+        ];
+    }
+
+    public static function parseHour24ClockOneBasedProvider()
+    {
+        return [
+            // 24 hours (1-24)
+            ['y-M-d k', '1970-1-1 1', 3600],
+            ['y-M-d k', '1970-1-1 10', 36000],
+            ['y-M-d kk', '1970-1-1 11', 39600],
+            ['y-M-d kk', '1970-1-1 12', 43200],
+            ['y-M-d kk', '1970-1-1 23', 82800],
+            ['y-M-d kk', '1970-1-1 24', 0],
+            ['y-M-d kk a', '1970-1-1 0 AM', 0],
+            ['y-M-d kk a', '1970-1-1 1 AM', 0],
+            ['y-M-d kk a', '1970-1-1 10 AM', 0],
+            ['y-M-d kk a', '1970-1-1 11 AM', 0],
+            ['y-M-d kk a', '1970-1-1 12 AM', 0],
+            ['y-M-d kk a', '1970-1-1 23 AM', 0],
+            ['y-M-d kk a', '1970-1-1 24 AM', 0],
+            ['y-M-d kk a', '1970-1-1 0 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 1 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 10 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 11 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 12 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 23 PM', 43200],
+            ['y-M-d kk a', '1970-1-1 24 PM', 43200],
+        ];
+    }
+
+    public static function parseHour24ClockZeroBasedProvider()
+    {
+        return [
+            // 24 hours (0-23)
+            ['y-M-d H', '1970-1-1 0', 0],
+            ['y-M-d H', '1970-1-1 1', 3600],
+            ['y-M-d H', '1970-1-1 10', 36000],
+            ['y-M-d HH', '1970-1-1 11', 39600],
+            ['y-M-d HH', '1970-1-1 12', 43200],
+            ['y-M-d HH', '1970-1-1 23', 82800],
+            ['y-M-d HH a', '1970-1-1 0 AM', 0],
+            ['y-M-d HH a', '1970-1-1 1 AM', 0],
+            ['y-M-d HH a', '1970-1-1 10 AM', 0],
+            ['y-M-d HH a', '1970-1-1 11 AM', 0],
+            ['y-M-d HH a', '1970-1-1 12 AM', 0],
+            ['y-M-d HH a', '1970-1-1 23 AM', 0],
+            ['y-M-d HH a', '1970-1-1 24 AM', 0],
+            ['y-M-d HH a', '1970-1-1 0 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 1 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 10 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 11 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 12 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 23 PM', 43200],
+            ['y-M-d HH a', '1970-1-1 24 PM', 43200],
+        ];
+    }
+
+    public static function parseMinuteProvider()
+    {
+        return [
+            ['y-M-d HH:m', '1970-1-1 0:1', 60],
+            ['y-M-d HH:mm', '1970-1-1 0:10', 600],
+        ];
+    }
+
+    public static function parseSecondProvider()
+    {
+        return [
+            ['y-M-d HH:mm:s', '1970-1-1 00:01:1', 61],
+            ['y-M-d HH:mm:ss', '1970-1-1 00:01:10', 70],
+        ];
+    }
+
+    public static function parseTimezoneProvider()
+    {
+        return [
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-03:00', 10800],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-04:00', 14400],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-00:00', 0],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+03:00', -10800],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+04:00', -14400],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT-0300', 10800],
+            ['y-M-d HH:mm:ss zzzz', '1970-1-1 00:00:00 GMT+0300', -10800],
+
+            // a previous timezone parsing should not change the timezone for the next parsing
+            ['y-M-d HH:mm:ss', '1970-1-1 00:00:00', 0],
+        ];
+    }
+
+    public static function parseAmPmProvider()
+    {
+        return [
+            // AM/PM (already covered by hours tests)
+            ['y-M-d HH:mm:ss a', '1970-1-1 00:00:00 AM', 0],
+            ['y-M-d HH:mm:ss a', '1970-1-1 00:00:00 PM', 43200],
+        ];
+    }
+
+    public static function parseStandaloneAmPmProvider()
+    {
+        return [
+            ['a', 'AM', 0],
+            ['a', 'PM', 43200],
+        ];
+    }
+
+    public static function parseRegexMetaCharsProvider()
+    {
+        return [
+            // regexp meta chars in the pattern string
+            ['y[M-d', '1970[1-1', 0],
+            ['y[M/d', '1970[1/1', 0],
+        ];
+    }
+
+    public static function parseQuoteCharsProvider()
+    {
+        return [
+            ["'M'", 'M', 0],
+            ["'yy'", 'yy', 0],
+            ["'''yy'", "'yy", 0],
+            ["''y", "'1970", 0],
+            ["H 'o'' clock'", "0 o' clock", 0],
+        ];
+    }
+
+    public static function parseDashSlashProvider()
+    {
+        return [
+            ['y-M-d', '1970/1/1', 0],
+            ['yy-M-d', '70/1/1', 0],
+            ['y/M/d', '1970-1-1', 0],
+            ['yy/M/d', '70-1-1', 0],
+        ];
+    }
+
+    /**
+     * @dataProvider parseErrorProvider
+     */
+    public function testParseError($pattern, $value)
+    {
+        $errorCode = Icu::U_PARSE_ERROR;
+        $errorMessage = 'Date parsing failed: U_PARSE_ERROR';
+
+        $formatter = $this->getDefaultDateFormatter($pattern);
+        $this->assertFalse($formatter->parse($value));
+        $this->assertIsIntlFailure($formatter, $errorMessage, $errorCode);
+    }
+
+    public static function parseErrorProvider()
+    {
+        return [
+            // 1 char month
+            ['y-MMMMM-d', '1970-J-1'],
+            ['y-MMMMM-d', '1970-S-1'],
+
+            // standalone 1 char month
+            ['y-LLLLL-d', '1970-J-1'],
+            ['y-LLLLL-d', '1970-S-1'],
+        ];
+    }
+
+    /*
+     * https://github.com/symfony/symfony/issues/4242
+     */
+    public function testParseAfterError()
+    {
+        $this->testParseError('y-MMMMM-d', '1970-J-1');
+        $this->testParse('y-M-d', '1970-1-1', 0);
+    }
+
+    public function testParseWithNullPositionValue()
+    {
+        $position = null;
+        $formatter = $this->getDefaultDateFormatter('y');
+        $this->assertSame(0, $formatter->parse('1970', $position));
+        // Since $position is not supported by the Symfony implementation, the following won't work.
+        // The intl implementation works this way since 60.2.
+        // $this->assertSame(4, $position);
+    }
+
+    public function testSetPattern()
+    {
+        $formatter = $this->getDefaultDateFormatter();
+        $formatter->setPattern('yyyy-MM-dd');
+        $this->assertEquals('yyyy-MM-dd', $formatter->getPattern());
+    }
+
+    /**
+     * @dataProvider setTimeZoneProvider
+     */
+    public function testSetTimeZone($timeZoneId, $expectedTimeZoneId)
+    {
+        $formatter = $this->getDefaultDateFormatter();
+
+        $formatter->setTimeZone($timeZoneId);
+
+        $this->assertEquals($expectedTimeZoneId, $formatter->getTimeZoneId());
+    }
+
+    public static function setTimeZoneProvider()
+    {
+        return [
+            ['UTC', 'UTC'],
+            ['GMT', 'GMT'],
+            ['GMT-03:00', 'GMT-03:00'],
+            ['Europe/Zurich', 'Europe/Zurich'],
+            [null, date_default_timezone_get()],
+            ['Foo/Bar', 'UTC'],
+            ['GMT+00:AA', 'UTC'],
+            ['GMT+00AA', 'UTC'],
+        ];
+    }
+
+    protected function getDefaultDateFormatter($pattern = null)
+    {
+        return $this->getDateFormatter('en', IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT, 'UTC', IntlDateFormatter::GREGORIAN, $pattern);
+    }
+
+    protected function getDateTime($timestamp, $timeZone)
+    {
+        $dateTime = new \DateTime();
+        $dateTime->setTimestamp(null === $timestamp ? time() : $timestamp);
+        $dateTime->setTimezone(new \DateTimeZone($timeZone ?: getenv('TZ') ?: 'UTC'));
+
+        return $dateTime;
+    }
+
+    protected function assertIsIntlFailure($formatter, $errorMessage, $errorCode)
+    {
+        $this->assertSame($errorMessage, $this->getIntlErrorMessage());
+        $this->assertSame($errorCode, $this->getIntlErrorCode());
+        $this->assertTrue($this->isIntlFailure($this->getIntlErrorCode()));
+        $this->assertSame($errorMessage, $formatter->getErrorMessage());
+        $this->assertSame($errorCode, $formatter->getErrorCode());
+        $this->assertTrue($this->isIntlFailure($formatter->getErrorCode()));
+    }
+
+    protected function assertIsIntlSuccess($formatter, $errorMessage, $errorCode)
+    {
+        /* @var IntlDateFormatter $formatter */
+        $this->assertSame($errorMessage, $this->getIntlErrorMessage());
+        $this->assertSame($errorCode, $this->getIntlErrorCode());
+        $this->assertFalse($this->isIntlFailure($this->getIntlErrorCode()));
+        $this->assertSame($errorMessage, $formatter->getErrorMessage());
+        $this->assertSame($errorCode, $formatter->getErrorCode());
+        $this->assertFalse($this->isIntlFailure($formatter->getErrorCode()));
+    }
+
+    /**
+     * @return IntlDateFormatter|\IntlDateFormatter
+     */
+    abstract protected function getDateFormatter($locale, $datetype, $timetype, $timezone = null, $calendar = IntlDateFormatter::GREGORIAN, $pattern = null);
+
+    abstract protected function getIntlErrorMessage(): string;
+
+    abstract protected function getIntlErrorCode(): int;
+
+    /**
+     * @param int $errorCode
+     */
+    abstract protected function isIntlFailure($errorCode): bool;
+}
diff --git a/tests/Intl/Icu/AbstractLocaleTest.php b/tests/Intl/Icu/AbstractLocaleTest.php
deleted file mode 100644
index 20dde71..0000000
--- a/tests/Intl/Icu/AbstractLocaleTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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\Polyfill\Tests\Intl\Icu;
-
-use PHPUnit\Framework\TestCase;
-
-/**
- * Test case for Locale implementations.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
-abstract class AbstractLocaleTest extends TestCase
-{
-    public function testSetDefault()
-    {
-        $this->call('setDefault', 'en_GB');
-
-        $this->assertSame('en_GB', $this->call('getDefault'));
-    }
-
-    abstract protected function call($methodName);
-}
diff --git a/tests/Intl/Icu/AbstractLocaleTestCase.php b/tests/Intl/Icu/AbstractLocaleTestCase.php
new file mode 100644
index 0000000..4ec15e2
--- /dev/null
+++ b/tests/Intl/Icu/AbstractLocaleTestCase.php
@@ -0,0 +1,31 @@
+<?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\Polyfill\Tests\Intl\Icu;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test case for Locale implementations.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+abstract class AbstractLocaleTestCase extends TestCase
+{
+    public function testSetDefault()
+    {
+        $this->call('setDefault', 'en_GB');
+
+        $this->assertSame('en_GB', $this->call('getDefault'));
+    }
+
+    abstract protected function call($methodName);
+}
diff --git a/tests/Intl/Icu/AbstractNumberFormatterTest.php b/tests/Intl/Icu/AbstractNumberFormatterTest.php
deleted file mode 100644
index e5f4707..0000000
--- a/tests/Intl/Icu/AbstractNumberFormatterTest.php
+++ /dev/null
@@ -1,899 +0,0 @@
-<?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\Polyfill\Tests\Intl\Icu;
-
-use PHPUnit\Framework\Error\Warning;
-use PHPUnit\Framework\TestCase;
-use Symfony\Polyfill\Intl\Icu\Icu;
-use Symfony\Polyfill\Intl\Icu\NumberFormatter;
-
-/**
- * Note that there are some values written like -2147483647 - 1. This is the lower 32bit int max and is a known
- * behavior of PHP.
- */
-abstract class AbstractNumberFormatterTest extends TestCase
-{
-    protected function setUp(): void
-    {
-        \Locale::setDefault('en');
-    }
-
-    /**
-     * @dataProvider formatCurrencyWithDecimalStyleProvider
-     */
-    public function testFormatCurrencyWithDecimalStyle($value, $currency, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $this->assertEquals($expected, $formatter->formatCurrency($value, $currency));
-    }
-
-    public static function formatCurrencyWithDecimalStyleProvider()
-    {
-        return [
-            [100, 'ALL', '100'],
-            [100, 'BRL', '100'],
-            [100, 'CRC', '100'],
-            [100, 'JPY', '100'],
-            [100, 'CHF', '100'],
-            [-100, 'ALL', '-100'],
-            [-100, 'BRL', '-100'],
-            [-100, 'CRC', '-100'],
-            [-100, 'JPY', '-100'],
-            [-100, 'CHF', '-100'],
-            [1000.12, 'ALL', '1,000.12'],
-            [1000.12, 'BRL', '1,000.12'],
-            [1000.12, 'CRC', '1,000.12'],
-            [1000.12, 'JPY', '1,000.12'],
-            [1000.12, 'CHF', '1,000.12'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatCurrencyWithCurrencyStyleProvider
-     */
-    public function testFormatCurrencyWithCurrencyStyle($value, $currency, $expected)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-        $this->assertEquals($expected, $formatter->formatCurrency($value, $currency));
-    }
-
-    public static function formatCurrencyWithCurrencyStyleProvider()
-    {
-        return [
-            [100, 'ALL', "ALL\xc2\xa0100"],
-            [-100, 'ALL', "-ALL\xc2\xa0100"],
-            [1000.12, 'ALL', "ALL\xc2\xa01,000"],
-
-            [100, 'JPY', '¥100'],
-            [-100, 'JPY', '-¥100'],
-            [1000.12, 'JPY', '¥1,000'],
-
-            [100, 'EUR', '€100.00'],
-            [-100, 'EUR', '-€100.00'],
-            [1000.12, 'EUR', '€1,000.12'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatCurrencyWithCurrencyStyleCostaRicanColonsRoundingProvider
-     */
-    public function testFormatCurrencyWithCurrencyStyleCostaRicanColonsRounding($value, $currency, $symbol, $expected)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
-    }
-
-    public static function formatCurrencyWithCurrencyStyleCostaRicanColonsRoundingProvider()
-    {
-        return [
-            [100, 'CRC', 'CRC', "%s\xc2\xa0100.00"],
-            [-100, 'CRC', 'CRC', "-%s\xc2\xa0100.00"],
-            [1000.12, 'CRC', 'CRC', "%s\xc2\xa01,000.12"],
-        ];
-    }
-
-    /**
-     * @dataProvider formatCurrencyWithCurrencyStyleBrazilianRealRoundingProvider
-     */
-    public function testFormatCurrencyWithCurrencyStyleBrazilianRealRounding($value, $currency, $symbol, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
-    }
-
-    public static function formatCurrencyWithCurrencyStyleBrazilianRealRoundingProvider()
-    {
-        return [
-            [100, 'BRL', 'R', '%s$100.00'],
-            [-100, 'BRL', 'R', '-%s$100.00'],
-            [1000.12, 'BRL', 'R', '%s$1,000.12'],
-
-            // Rounding checks
-            [1000.121, 'BRL', 'R', '%s$1,000.12'],
-            [1000.123, 'BRL', 'R', '%s$1,000.12'],
-            [1000.125, 'BRL', 'R', '%s$1,000.12'],
-            [1000.127, 'BRL', 'R', '%s$1,000.13'],
-            [1000.129, 'BRL', 'R', '%s$1,000.13'],
-            [11.50999, 'BRL', 'R', '%s$11.51'],
-            [11.9999464, 'BRL', 'R', '%s$12.00'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatCurrencyWithCurrencyStyleSwissRoundingProvider
-     */
-    public function testFormatCurrencyWithCurrencyStyleSwissRounding($value, $currency, $symbol, $expected)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
-            $this->markTestSkipped('ICU version 62.1 is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
-    }
-
-    public static function formatCurrencyWithCurrencyStyleSwissRoundingProvider()
-    {
-        return [
-            [100, 'CHF', 'CHF', "%s\xc2\xa0100.00"],
-            [-100, 'CHF', 'CHF', "-%s\xc2\xa0100.00"],
-            [1000.12, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
-            ['1000.12', 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
-
-            // Rounding checks
-            [1000.121, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
-            [1000.123, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
-            [1000.125, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
-            [1000.127, 'CHF', 'CHF', "%s\xc2\xa01,000.13"],
-            [1000.129, 'CHF', 'CHF', "%s\xc2\xa01,000.13"],
-
-            [1200000.00, 'CHF', 'CHF', "%s\xc2\xa01,200,000.00"],
-            [1200000.1, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
-            [1200000.10, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
-            [1200000.101, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
-        ];
-    }
-
-    public function testFormat()
-    {
-        $errorCode = Icu::U_ZERO_ERROR;
-        $errorMessage = 'U_ZERO_ERROR';
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $this->assertSame('9.555', $formatter->format(9.555));
-
-        $this->assertSame($errorMessage, static::getIntlErrorMessage());
-        $this->assertSame($errorCode, static::getIntlErrorCode());
-        $this->assertFalse(static::isIntlFailure(static::getIntlErrorCode()));
-        $this->assertSame($errorMessage, $formatter->getErrorMessage());
-        $this->assertSame($errorCode, $formatter->getErrorCode());
-        $this->assertFalse(static::isIntlFailure($formatter->getErrorCode()));
-    }
-
-    public function testFormatWithCurrencyStyle()
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-        $this->assertEquals('¤1.00', $formatter->format(1));
-    }
-
-    /**
-     * @dataProvider formatTypeInt32Provider
-     */
-    public function testFormatTypeInt32($formatter, $value, $expected, $message = '')
-    {
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT32);
-        $this->assertEquals($expected, $formattedValue, $message);
-    }
-
-    public static function formatTypeInt32Provider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $message = '->format() TYPE_INT32 formats inconsistently an integer if out of the 32 bit range.';
-
-        return [
-            [$formatter, 1, '1'],
-            [$formatter, 1.1, '1'],
-            [$formatter, 2147483648, '-2,147,483,648', $message],
-            [$formatter, -2147483649, '2,147,483,647', $message],
-        ];
-    }
-
-    /**
-     * @dataProvider formatTypeInt32WithCurrencyStyleProvider
-     */
-    public function testFormatTypeInt32WithCurrencyStyle($formatter, $value, $expected, $message = '')
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT32);
-        $this->assertEquals($expected, $formattedValue, $message);
-    }
-
-    public static function formatTypeInt32WithCurrencyStyleProvider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        $message = '->format() TYPE_INT32 formats inconsistently an integer if out of the 32 bit range.';
-
-        return [
-            [$formatter, 1, '¤1.00'],
-            [$formatter, 1.1, '¤1.00'],
-            [$formatter, 2147483648, '-¤2,147,483,648.00', $message],
-            [$formatter, -2147483649, '¤2,147,483,647.00', $message],
-        ];
-    }
-
-    /**
-     * The parse() method works differently with integer out of the 32 bit range. format() works fine.
-     *
-     * @dataProvider formatTypeInt64Provider
-     */
-    public function testFormatTypeInt64($formatter, $value, $expected)
-    {
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT64);
-        $this->assertEquals($expected, $formattedValue);
-    }
-
-    public static function formatTypeInt64Provider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        return [
-            [$formatter, 1, '1'],
-            [$formatter, 1.1, '1'],
-            [$formatter, 2147483648, '2,147,483,648'],
-            [$formatter, -2147483649, '-2,147,483,649'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatTypeInt64WithCurrencyStyleProvider
-     */
-    public function testFormatTypeInt64WithCurrencyStyle($formatter, $value, $expected)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT64);
-        $this->assertEquals($expected, $formattedValue);
-    }
-
-    public static function formatTypeInt64WithCurrencyStyleProvider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        return [
-            [$formatter, 1, '¤1.00'],
-            [$formatter, 1.1, '¤1.00'],
-            [$formatter, 2147483648, '¤2,147,483,648.00'],
-            [$formatter, -2147483649, '-¤2,147,483,649.00'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatTypeDoubleProvider
-     */
-    public function testFormatTypeDouble($formatter, $value, $expected)
-    {
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_DOUBLE);
-        $this->assertEquals($expected, $formattedValue);
-    }
-
-    public static function formatTypeDoubleProvider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        return [
-            [$formatter, 1, '1'],
-            [$formatter, 1.1, '1.1'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatTypeDoubleWithCurrencyStyleProvider
-     */
-    public function testFormatTypeDoubleWithCurrencyStyle($formatter, $value, $expected)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_DOUBLE);
-        $this->assertEquals($expected, $formattedValue);
-    }
-
-    public static function formatTypeDoubleWithCurrencyStyleProvider()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        return [
-            [$formatter, 1, '¤1.00'],
-            [$formatter, 1.1, '¤1.10'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatTypeCurrencyProvider
-     */
-    public function testFormatTypeCurrency($formatter, $value)
-    {
-        if (\PHP_VERSION_ID >= 80000) {
-            $this->expectException(\ValueError::class);
-        } elseif (method_exists($this, 'expectWarning')) {
-            $this->expectWarning();
-        } else {
-            $this->expectException(Warning::class);
-        }
-
-        $formatter->format($value, NumberFormatter::TYPE_CURRENCY);
-    }
-
-    /**
-     * @dataProvider formatTypeCurrencyProvider
-     */
-    public function testFormatTypeCurrencyReturn($formatter, $value)
-    {
-        if (\PHP_VERSION_ID >= 80000) {
-            $this->expectException(\ValueError::class);
-        }
-
-        $this->assertFalse(@$formatter->format($value, NumberFormatter::TYPE_CURRENCY));
-    }
-
-    public static function formatTypeCurrencyProvider()
-    {
-        $df = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $cf = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        return [
-            [$df, 1],
-            [$cf, 1],
-        ];
-    }
-
-    /**
-     * @dataProvider formatFractionDigitsProvider
-     */
-    public function testFormatFractionDigits($value, $expected, $fractionDigits = null, $expectedFractionDigits = 1)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
-            $this->markTestSkipped('ICU version 62.1 is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $attributeRet = null;
-        if (null !== $fractionDigits) {
-            $attributeRet = $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $fractionDigits);
-        }
-
-        $formattedValue = $formatter->format($value);
-        $this->assertSame($expected, $formattedValue);
-        $this->assertSame($expectedFractionDigits, $formatter->getAttribute(NumberFormatter::FRACTION_DIGITS));
-
-        if (null !== $attributeRet) {
-            $this->assertTrue($attributeRet);
-        }
-    }
-
-    public static function formatFractionDigitsProvider()
-    {
-        yield [1.123, '1.123', null, 0];
-        yield [1.123, '1', 0, 0];
-        yield [1.123, '1.1', 1, 1];
-        yield [1.123, '1.12', 2, 2];
-        yield [1.123, '1.123', -1, 0];
-    }
-
-    /**
-     * @dataProvider formatGroupingUsedProvider
-     */
-    public function testFormatGroupingUsed($value, $expected, $groupingUsed = null, $expectedGroupingUsed = 1)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $attributeRet = null;
-        if (null !== $groupingUsed) {
-            $attributeRet = $formatter->setAttribute(NumberFormatter::GROUPING_USED, $groupingUsed);
-        }
-
-        $formattedValue = $formatter->format($value);
-        $this->assertSame($expected, $formattedValue);
-        $this->assertSame($expectedGroupingUsed, $formatter->getAttribute(NumberFormatter::GROUPING_USED));
-
-        if (null !== $attributeRet) {
-            $this->assertTrue($attributeRet);
-        }
-    }
-
-    public static function formatGroupingUsedProvider()
-    {
-        yield [1000, '1,000', null, 1];
-        yield [1000, '1000', 0, 0];
-        yield [1000, '1,000', 1, 1];
-        yield [1000, '1,000', 2, 1];
-        yield [1000, '1,000', -1, 1];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundHalfUpProvider
-     */
-    public function testFormatRoundingModeHalfUp($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFUP);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFUP rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundHalfUpProvider()
-    {
-        // The commented value is differently rounded by intl's NumberFormatter in 32 and 64 bit architectures
-        return [
-            [1.121, '1.12'],
-            [1.123, '1.12'],
-            // [1.125, '1.13'],
-            [1.127, '1.13'],
-            [1.129, '1.13'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundHalfDownProvider
-     */
-    public function testFormatRoundingModeHalfDown($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFDOWN);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFDOWN rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundHalfDownProvider()
-    {
-        return [
-            [1.121, '1.12'],
-            [1.123, '1.12'],
-            [1.125, '1.12'],
-            [1.127, '1.13'],
-            [1.129, '1.13'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundHalfEvenProvider
-     */
-    public function testFormatRoundingModeHalfEven($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFEVEN);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFEVEN rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundHalfEvenProvider()
-    {
-        return [
-            [1.121, '1.12'],
-            [1.123, '1.12'],
-            [1.125, '1.12'],
-            [1.127, '1.13'],
-            [1.129, '1.13'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundCeilingProvider
-     */
-    public function testFormatRoundingModeCeiling($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_CEILING);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_CEILING rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundCeilingProvider()
-    {
-        return [
-            [1.123, '1.13'],
-            [1.125, '1.13'],
-            [1.127, '1.13'],
-            [-1.123, '-1.12'],
-            [-1.125, '-1.12'],
-            [-1.127, '-1.12'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundFloorProvider
-     */
-    public function testFormatRoundingModeFloor($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_FLOOR);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_FLOOR rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundFloorProvider()
-    {
-        return [
-            [1.123, '1.12'],
-            [1.125, '1.12'],
-            [1.127, '1.12'],
-            [-1.123, '-1.13'],
-            [-1.125, '-1.13'],
-            [-1.127, '-1.13'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundDownProvider
-     */
-    public function testFormatRoundingModeDown($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_DOWN);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_DOWN rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundDownProvider()
-    {
-        return [
-            [1.123, '1.12'],
-            [1.125, '1.12'],
-            [1.127, '1.12'],
-            [-1.123, '-1.12'],
-            [-1.125, '-1.12'],
-            [-1.127, '-1.12'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    /**
-     * @dataProvider formatRoundingModeRoundUpProvider
-     */
-    public function testFormatRoundingModeUp($value, $expected)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
-
-        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_UP);
-        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_UP rounding mode.');
-    }
-
-    public static function formatRoundingModeRoundUpProvider()
-    {
-        return [
-            [1.123, '1.13'],
-            [1.125, '1.13'],
-            [1.127, '1.13'],
-            [-1.123, '-1.13'],
-            [-1.125, '-1.13'],
-            [-1.127, '-1.13'],
-            [1020 / 100, '10.20'],
-        ];
-    }
-
-    public function testGetLocale()
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $this->assertEquals('en', $formatter->getLocale());
-    }
-
-    public function testGetSymbol()
-    {
-        $decimalFormatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $currencyFormatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        $r = new \ReflectionProperty('Symfony\Polyfill\Intl\Icu\NumberFormatter', 'enSymbols');
-        $r->setAccessible(true);
-        $expected = $r->getValue();
-
-        for ($i = 0; $i <= 17; ++$i) {
-            $this->assertSame($expected[1][$i], $decimalFormatter->getSymbol($i));
-            $this->assertSame($expected[2][$i], $currencyFormatter->getSymbol($i));
-        }
-    }
-
-    public function testGetTextAttribute()
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
-            $this->markTestSkipped('ICU version 63.1 is required.');
-        }
-
-        $decimalFormatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $currencyFormatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
-
-        $r = new \ReflectionProperty('Symfony\Polyfill\Intl\Icu\NumberFormatter', 'enTextAttributes');
-        $r->setAccessible(true);
-        $expected = $r->getValue();
-
-        for ($i = 0; $i <= 5; ++$i) {
-            $this->assertSame($expected[1][$i], $decimalFormatter->getTextAttribute($i));
-            $this->assertSame($expected[2][$i], $currencyFormatter->getTextAttribute($i));
-        }
-    }
-
-    /**
-     * @dataProvider parseProvider
-     */
-    public function testParse($value, $expected, $message, $expectedPosition, $groupingUsed = true)
-    {
-        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
-            $this->markTestSkipped('ICU version 62.1 is required.');
-        }
-
-        $position = 0;
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->setAttribute(NumberFormatter::GROUPING_USED, $groupingUsed);
-        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_DOUBLE, $position);
-        $this->assertSame($expected, $parsedValue, $message);
-        $this->assertSame($expectedPosition, $position, $message);
-
-        if (false === $expected) {
-            $errorCode = Icu::U_PARSE_ERROR;
-            $errorMessage = 'Number parsing failed: U_PARSE_ERROR';
-        } else {
-            $errorCode = Icu::U_ZERO_ERROR;
-            $errorMessage = 'U_ZERO_ERROR';
-        }
-
-        $this->assertSame($errorMessage, static::getIntlErrorMessage());
-        $this->assertSame($errorCode, static::getIntlErrorCode());
-        $this->assertSame(0 !== $errorCode, static::isIntlFailure(static::getIntlErrorCode()));
-        $this->assertSame($errorMessage, $formatter->getErrorMessage());
-        $this->assertSame($errorCode, $formatter->getErrorCode());
-        $this->assertSame(0 !== $errorCode, static::isIntlFailure($formatter->getErrorCode()));
-    }
-
-    public static function parseProvider()
-    {
-        return [
-            ['prefix1', false, '->parse() does not parse a number with a string prefix.', 0],
-            ['prefix1', false, '->parse() does not parse a number with a string prefix.', 0, false],
-            ['1.4suffix', (float) 1.4, '->parse() parses a number with a string suffix.', 3],
-            ['1.4suffix', (float) 1.4, '->parse() parses a number with a string suffix.', 3, false],
-            ['1,234.4suffix', 1234.4, '->parse() parses a number with a string suffix.', 7],
-            ['1,234.4suffix', 1.0, '->parse() parses a number with a string suffix.', 1, false],
-            ['-.4suffix', (float) -0.4, '->parse() parses a negative dot float with suffix.', 3],
-            ['-.4suffix', (float) -0.4, '->parse() parses a negative dot float with suffix.', 3, false],
-            [',4', false, '->parse() does not parse when invalid grouping used.', 0],
-            [',4', false, '->parse() does not parse when invalid grouping used.', 0, false],
-            ['123,4', false, '->parse() does not parse when invalid grouping used.', 0],
-            ['123,4', 123.0, '->parse() truncates invalid grouping when grouping is disabled.', 3, false],
-            ['123,a4', 123.0, '->parse() truncates a string suffix.', 3],
-            ['123,a4', 123.0, '->parse() truncates a string suffix.', 3, false],
-            ['-123,4', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123,4', -123.0, '->parse() truncates invalid grouping when grouping is disabled.', 4, false],
-            ['-123,4567', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123,4567', -123.0, '->parse() truncates invalid grouping when grouping is disabled.', 4, false],
-            ['-123,456,789', -123456789.0, '->parse() parses a number with grouping.', 12],
-            ['-123,456,789', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
-            ['-123,456,789.66', -123456789.66, '->parse() parses a number with grouping.', 15],
-            ['-123,456,789.66', -123.00, '->parse() truncates a group if grouping is disabled.', 4, false],
-            ['-123,456789.66', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123,456789.66', -123.00, '->parse() truncates a group if grouping is disabled.', 4, false],
-            ['-123456,789.66', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123456,789.66', -123456.00, '->parse() truncates a group if grouping is disabled.', 7, false],
-            ['-123,456,78', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123,456,78', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
-            ['-123,45,789', false, '->parse() does not parse when invalid grouping used.', 1],
-            ['-123,45,789', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
-            ['-123,,456', -123.0, '->parse() parses when grouping is duplicated.', 4],
-            ['-123,,456', -123.0, '->parse() parses when grouping is disabled.', 4, false],
-            ['-123,,4', -123.0, '->parse() parses when grouping is duplicated.', 4],
-            ['-123,,4', -123.0, '->parse() parses when grouping is duplicated.', 4, false],
-            ['239.', 239.0, '->parse() parses when string ends with decimal separator.', 4],
-            ['239.', 239.0, '->parse() parses when string ends with decimal separator.', 4, false],
-        ];
-    }
-
-    public function testParseTypeDefault()
-    {
-        if (\PHP_VERSION_ID >= 80000) {
-            $this->expectException(\ValueError::class);
-        } elseif (method_exists($this, 'expectWarning')) {
-            $this->expectWarning();
-        } else {
-            $this->expectException(Warning::class);
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->parse('1', NumberFormatter::TYPE_DEFAULT);
-    }
-
-    /**
-     * @dataProvider parseTypeInt32Provider
-     */
-    public function testParseTypeInt32($value, $expected, $message = '')
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_INT32);
-        $this->assertSame($expected, $parsedValue, $message);
-    }
-
-    public static function parseTypeInt32Provider()
-    {
-        return [
-            ['1', 1],
-            ['1.1', 1],
-            ['.1', 0],
-            ['2,147,483,647', 2147483647],
-            ['-2,147,483,648', -2147483647 - 1],
-            ['2,147,483,648', false, '->parse() TYPE_INT32 returns false when the number is greater than the integer positive range.'],
-            ['-2,147,483,649', false, '->parse() TYPE_INT32 returns false when the number is greater than the integer negative range.'],
-        ];
-    }
-
-    public function testParseTypeInt64With32BitIntegerInPhp32Bit()
-    {
-        if (4 !== \PHP_INT_SIZE) {
-            $this->markTestSkipped('PHP 32 bit is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $parsedValue = $formatter->parse('2,147,483,647', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-        $this->assertEquals(2147483647, $parsedValue);
-
-        $parsedValue = $formatter->parse('-2,147,483,648', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-        $this->assertEquals(-2147483648, $parsedValue);
-    }
-
-    public function testParseTypeInt64With32BitIntegerInPhp64Bit()
-    {
-        if (8 !== \PHP_INT_SIZE) {
-            $this->markTestSkipped('PHP 64 bit is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $parsedValue = $formatter->parse('2,147,483,647', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-        $this->assertEquals(2147483647, $parsedValue);
-
-        $parsedValue = $formatter->parse('-2,147,483,648', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-        $this->assertEquals(-2147483647 - 1, $parsedValue);
-    }
-
-    /**
-     * If PHP is compiled in 32bit mode, the returned value for a 64bit integer are float numbers.
-     */
-    public function testParseTypeInt64With64BitIntegerInPhp32Bit()
-    {
-        if (4 !== \PHP_INT_SIZE) {
-            $this->markTestSkipped('PHP 32 bit is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        // int 64 using only 32 bit range strangeness
-        $parsedValue = $formatter->parse('2,147,483,648', NumberFormatter::TYPE_INT64);
-        $this->assertIsFloat($parsedValue);
-        $this->assertEquals(2147483648, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.');
-
-        $parsedValue = $formatter->parse('-2,147,483,649', NumberFormatter::TYPE_INT64);
-        $this->assertIsFloat($parsedValue);
-        $this->assertEquals(-2147483649, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.');
-    }
-
-    /**
-     * If PHP is compiled in 64bit mode, the returned value for a 64bit integer are 32bit integer numbers.
-     */
-    public function testParseTypeInt64With64BitIntegerInPhp64Bit()
-    {
-        if (8 !== \PHP_INT_SIZE) {
-            $this->markTestSkipped('PHP 64 bit is required.');
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-
-        $parsedValue = $formatter->parse('2,147,483,648', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-
-        $this->assertEquals(2147483648, $parsedValue, '->parse() TYPE_INT64 uses true 64 bit integers (PHP >= 5.3.14 and PHP >= 5.4.4).');
-
-        $parsedValue = $formatter->parse('-2,147,483,649', NumberFormatter::TYPE_INT64);
-        $this->assertIsInt($parsedValue);
-
-        $this->assertEquals(-2147483649, $parsedValue, '->parse() TYPE_INT64 uses true 64 bit integers (PHP >= 5.3.14 and PHP >= 5.4.4).');
-    }
-
-    /**
-     * @dataProvider parseTypeDoubleProvider
-     */
-    public function testParseTypeDouble($value, $expectedValue)
-    {
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_DOUBLE);
-        $this->assertEqualsWithDelta($expectedValue, $parsedValue, 0.001);
-    }
-
-    public static function parseTypeDoubleProvider()
-    {
-        return [
-            ['1', (float) 1],
-            ['1.1', 1.1],
-            ['9,223,372,036,854,775,808', 9223372036854775808],
-            ['-9,223,372,036,854,775,809', -9223372036854775809],
-        ];
-    }
-
-    public function testParseTypeCurrency()
-    {
-        if (\PHP_VERSION_ID >= 80000) {
-            $this->expectException(\ValueError::class);
-        } elseif (method_exists($this, 'expectWarning')) {
-            $this->expectWarning();
-        } else {
-            $this->expectException(Warning::class);
-        }
-
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->parse('1', NumberFormatter::TYPE_CURRENCY);
-    }
-
-    public function testParseWithNotNullPositionValue()
-    {
-        $position = 1;
-        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
-        $formatter->parse('123', NumberFormatter::TYPE_DOUBLE, $position);
-        $this->assertEquals(3, $position);
-    }
-
-    /**
-     * @return NumberFormatter|\NumberFormatter
-     */
-    abstract protected static function getNumberFormatter(string $locale = 'en', ?string $style = null, ?string $pattern = null);
-
-    abstract protected static function getIntlErrorMessage(): string;
-
-    abstract protected static function getIntlErrorCode(): int;
-
-    /**
-     * @param int $errorCode
-     */
-    abstract protected static function isIntlFailure($errorCode): bool;
-}
diff --git a/tests/Intl/Icu/AbstractNumberFormatterTestCase.php b/tests/Intl/Icu/AbstractNumberFormatterTestCase.php
new file mode 100644
index 0000000..8dbc41c
--- /dev/null
+++ b/tests/Intl/Icu/AbstractNumberFormatterTestCase.php
@@ -0,0 +1,899 @@
+<?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\Polyfill\Tests\Intl\Icu;
+
+use PHPUnit\Framework\Error\Warning;
+use PHPUnit\Framework\TestCase;
+use Symfony\Polyfill\Intl\Icu\Icu;
+use Symfony\Polyfill\Intl\Icu\NumberFormatter;
+
+/**
+ * Note that there are some values written like -2147483647 - 1. This is the lower 32bit int max and is a known
+ * behavior of PHP.
+ */
+abstract class AbstractNumberFormatterTestCase extends TestCase
+{
+    protected function setUp(): void
+    {
+        \Locale::setDefault('en');
+    }
+
+    /**
+     * @dataProvider formatCurrencyWithDecimalStyleProvider
+     */
+    public function testFormatCurrencyWithDecimalStyle($value, $currency, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $this->assertEquals($expected, $formatter->formatCurrency($value, $currency));
+    }
+
+    public static function formatCurrencyWithDecimalStyleProvider()
+    {
+        return [
+            [100, 'ALL', '100'],
+            [100, 'BRL', '100'],
+            [100, 'CRC', '100'],
+            [100, 'JPY', '100'],
+            [100, 'CHF', '100'],
+            [-100, 'ALL', '-100'],
+            [-100, 'BRL', '-100'],
+            [-100, 'CRC', '-100'],
+            [-100, 'JPY', '-100'],
+            [-100, 'CHF', '-100'],
+            [1000.12, 'ALL', '1,000.12'],
+            [1000.12, 'BRL', '1,000.12'],
+            [1000.12, 'CRC', '1,000.12'],
+            [1000.12, 'JPY', '1,000.12'],
+            [1000.12, 'CHF', '1,000.12'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatCurrencyWithCurrencyStyleProvider
+     */
+    public function testFormatCurrencyWithCurrencyStyle($value, $currency, $expected)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+        $this->assertEquals($expected, $formatter->formatCurrency($value, $currency));
+    }
+
+    public static function formatCurrencyWithCurrencyStyleProvider()
+    {
+        return [
+            [100, 'ALL', "ALL\xc2\xa0100"],
+            [-100, 'ALL', "-ALL\xc2\xa0100"],
+            [1000.12, 'ALL', "ALL\xc2\xa01,000"],
+
+            [100, 'JPY', '¥100'],
+            [-100, 'JPY', '-¥100'],
+            [1000.12, 'JPY', '¥1,000'],
+
+            [100, 'EUR', '€100.00'],
+            [-100, 'EUR', '-€100.00'],
+            [1000.12, 'EUR', '€1,000.12'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatCurrencyWithCurrencyStyleCostaRicanColonsRoundingProvider
+     */
+    public function testFormatCurrencyWithCurrencyStyleCostaRicanColonsRounding($value, $currency, $symbol, $expected)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
+    }
+
+    public static function formatCurrencyWithCurrencyStyleCostaRicanColonsRoundingProvider()
+    {
+        return [
+            [100, 'CRC', 'CRC', "%s\xc2\xa0100.00"],
+            [-100, 'CRC', 'CRC', "-%s\xc2\xa0100.00"],
+            [1000.12, 'CRC', 'CRC', "%s\xc2\xa01,000.12"],
+        ];
+    }
+
+    /**
+     * @dataProvider formatCurrencyWithCurrencyStyleBrazilianRealRoundingProvider
+     */
+    public function testFormatCurrencyWithCurrencyStyleBrazilianRealRounding($value, $currency, $symbol, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
+    }
+
+    public static function formatCurrencyWithCurrencyStyleBrazilianRealRoundingProvider()
+    {
+        return [
+            [100, 'BRL', 'R', '%s$100.00'],
+            [-100, 'BRL', 'R', '-%s$100.00'],
+            [1000.12, 'BRL', 'R', '%s$1,000.12'],
+
+            // Rounding checks
+            [1000.121, 'BRL', 'R', '%s$1,000.12'],
+            [1000.123, 'BRL', 'R', '%s$1,000.12'],
+            [1000.125, 'BRL', 'R', '%s$1,000.12'],
+            [1000.127, 'BRL', 'R', '%s$1,000.13'],
+            [1000.129, 'BRL', 'R', '%s$1,000.13'],
+            [11.50999, 'BRL', 'R', '%s$11.51'],
+            [11.9999464, 'BRL', 'R', '%s$12.00'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatCurrencyWithCurrencyStyleSwissRoundingProvider
+     */
+    public function testFormatCurrencyWithCurrencyStyleSwissRounding($value, $currency, $symbol, $expected)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
+            $this->markTestSkipped('ICU version 62.1 is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+        $this->assertEquals(sprintf($expected, $symbol), $formatter->formatCurrency($value, $currency));
+    }
+
+    public static function formatCurrencyWithCurrencyStyleSwissRoundingProvider()
+    {
+        return [
+            [100, 'CHF', 'CHF', "%s\xc2\xa0100.00"],
+            [-100, 'CHF', 'CHF', "-%s\xc2\xa0100.00"],
+            [1000.12, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
+            ['1000.12', 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
+
+            // Rounding checks
+            [1000.121, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
+            [1000.123, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
+            [1000.125, 'CHF', 'CHF', "%s\xc2\xa01,000.12"],
+            [1000.127, 'CHF', 'CHF', "%s\xc2\xa01,000.13"],
+            [1000.129, 'CHF', 'CHF', "%s\xc2\xa01,000.13"],
+
+            [1200000.00, 'CHF', 'CHF', "%s\xc2\xa01,200,000.00"],
+            [1200000.1, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
+            [1200000.10, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
+            [1200000.101, 'CHF', 'CHF', "%s\xc2\xa01,200,000.10"],
+        ];
+    }
+
+    public function testFormat()
+    {
+        $errorCode = Icu::U_ZERO_ERROR;
+        $errorMessage = 'U_ZERO_ERROR';
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $this->assertSame('9.555', $formatter->format(9.555));
+
+        $this->assertSame($errorMessage, static::getIntlErrorMessage());
+        $this->assertSame($errorCode, static::getIntlErrorCode());
+        $this->assertFalse(static::isIntlFailure(static::getIntlErrorCode()));
+        $this->assertSame($errorMessage, $formatter->getErrorMessage());
+        $this->assertSame($errorCode, $formatter->getErrorCode());
+        $this->assertFalse(static::isIntlFailure($formatter->getErrorCode()));
+    }
+
+    public function testFormatWithCurrencyStyle()
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+        $this->assertEquals('¤1.00', $formatter->format(1));
+    }
+
+    /**
+     * @dataProvider formatTypeInt32Provider
+     */
+    public function testFormatTypeInt32($formatter, $value, $expected, $message = '')
+    {
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT32);
+        $this->assertEquals($expected, $formattedValue, $message);
+    }
+
+    public static function formatTypeInt32Provider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $message = '->format() TYPE_INT32 formats inconsistently an integer if out of the 32 bit range.';
+
+        return [
+            [$formatter, 1, '1'],
+            [$formatter, 1.1, '1'],
+            [$formatter, 2147483648, '-2,147,483,648', $message],
+            [$formatter, -2147483649, '2,147,483,647', $message],
+        ];
+    }
+
+    /**
+     * @dataProvider formatTypeInt32WithCurrencyStyleProvider
+     */
+    public function testFormatTypeInt32WithCurrencyStyle($formatter, $value, $expected, $message = '')
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT32);
+        $this->assertEquals($expected, $formattedValue, $message);
+    }
+
+    public static function formatTypeInt32WithCurrencyStyleProvider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        $message = '->format() TYPE_INT32 formats inconsistently an integer if out of the 32 bit range.';
+
+        return [
+            [$formatter, 1, '¤1.00'],
+            [$formatter, 1.1, '¤1.00'],
+            [$formatter, 2147483648, '-¤2,147,483,648.00', $message],
+            [$formatter, -2147483649, '¤2,147,483,647.00', $message],
+        ];
+    }
+
+    /**
+     * The parse() method works differently with integer out of the 32 bit range. format() works fine.
+     *
+     * @dataProvider formatTypeInt64Provider
+     */
+    public function testFormatTypeInt64($formatter, $value, $expected)
+    {
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT64);
+        $this->assertEquals($expected, $formattedValue);
+    }
+
+    public static function formatTypeInt64Provider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        return [
+            [$formatter, 1, '1'],
+            [$formatter, 1.1, '1'],
+            [$formatter, 2147483648, '2,147,483,648'],
+            [$formatter, -2147483649, '-2,147,483,649'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatTypeInt64WithCurrencyStyleProvider
+     */
+    public function testFormatTypeInt64WithCurrencyStyle($formatter, $value, $expected)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_INT64);
+        $this->assertEquals($expected, $formattedValue);
+    }
+
+    public static function formatTypeInt64WithCurrencyStyleProvider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        return [
+            [$formatter, 1, '¤1.00'],
+            [$formatter, 1.1, '¤1.00'],
+            [$formatter, 2147483648, '¤2,147,483,648.00'],
+            [$formatter, -2147483649, '-¤2,147,483,649.00'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatTypeDoubleProvider
+     */
+    public function testFormatTypeDouble($formatter, $value, $expected)
+    {
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_DOUBLE);
+        $this->assertEquals($expected, $formattedValue);
+    }
+
+    public static function formatTypeDoubleProvider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        return [
+            [$formatter, 1, '1'],
+            [$formatter, 1.1, '1.1'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatTypeDoubleWithCurrencyStyleProvider
+     */
+    public function testFormatTypeDoubleWithCurrencyStyle($formatter, $value, $expected)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $formattedValue = $formatter->format($value, NumberFormatter::TYPE_DOUBLE);
+        $this->assertEquals($expected, $formattedValue);
+    }
+
+    public static function formatTypeDoubleWithCurrencyStyleProvider()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        return [
+            [$formatter, 1, '¤1.00'],
+            [$formatter, 1.1, '¤1.10'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatTypeCurrencyProvider
+     */
+    public function testFormatTypeCurrency($formatter, $value)
+    {
+        if (\PHP_VERSION_ID >= 80000) {
+            $this->expectException(\ValueError::class);
+        } elseif (method_exists($this, 'expectWarning')) {
+            $this->expectWarning();
+        } else {
+            $this->expectException(Warning::class);
+        }
+
+        $formatter->format($value, NumberFormatter::TYPE_CURRENCY);
+    }
+
+    /**
+     * @dataProvider formatTypeCurrencyProvider
+     */
+    public function testFormatTypeCurrencyReturn($formatter, $value)
+    {
+        if (\PHP_VERSION_ID >= 80000) {
+            $this->expectException(\ValueError::class);
+        }
+
+        $this->assertFalse(@$formatter->format($value, NumberFormatter::TYPE_CURRENCY));
+    }
+
+    public static function formatTypeCurrencyProvider()
+    {
+        $df = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $cf = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        return [
+            [$df, 1],
+            [$cf, 1],
+        ];
+    }
+
+    /**
+     * @dataProvider formatFractionDigitsProvider
+     */
+    public function testFormatFractionDigits($value, $expected, $fractionDigits = null, $expectedFractionDigits = 1)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
+            $this->markTestSkipped('ICU version 62.1 is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $attributeRet = null;
+        if (null !== $fractionDigits) {
+            $attributeRet = $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $fractionDigits);
+        }
+
+        $formattedValue = $formatter->format($value);
+        $this->assertSame($expected, $formattedValue);
+        $this->assertSame($expectedFractionDigits, $formatter->getAttribute(NumberFormatter::FRACTION_DIGITS));
+
+        if (null !== $attributeRet) {
+            $this->assertTrue($attributeRet);
+        }
+    }
+
+    public static function formatFractionDigitsProvider()
+    {
+        yield [1.123, '1.123', null, 0];
+        yield [1.123, '1', 0, 0];
+        yield [1.123, '1.1', 1, 1];
+        yield [1.123, '1.12', 2, 2];
+        yield [1.123, '1.123', -1, 0];
+    }
+
+    /**
+     * @dataProvider formatGroupingUsedProvider
+     */
+    public function testFormatGroupingUsed($value, $expected, $groupingUsed = null, $expectedGroupingUsed = 1)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $attributeRet = null;
+        if (null !== $groupingUsed) {
+            $attributeRet = $formatter->setAttribute(NumberFormatter::GROUPING_USED, $groupingUsed);
+        }
+
+        $formattedValue = $formatter->format($value);
+        $this->assertSame($expected, $formattedValue);
+        $this->assertSame($expectedGroupingUsed, $formatter->getAttribute(NumberFormatter::GROUPING_USED));
+
+        if (null !== $attributeRet) {
+            $this->assertTrue($attributeRet);
+        }
+    }
+
+    public static function formatGroupingUsedProvider()
+    {
+        yield [1000, '1,000', null, 1];
+        yield [1000, '1000', 0, 0];
+        yield [1000, '1,000', 1, 1];
+        yield [1000, '1,000', 2, 1];
+        yield [1000, '1,000', -1, 1];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundHalfUpProvider
+     */
+    public function testFormatRoundingModeHalfUp($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFUP);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFUP rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundHalfUpProvider()
+    {
+        // The commented value is differently rounded by intl's NumberFormatter in 32 and 64 bit architectures
+        return [
+            [1.121, '1.12'],
+            [1.123, '1.12'],
+            // [1.125, '1.13'],
+            [1.127, '1.13'],
+            [1.129, '1.13'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundHalfDownProvider
+     */
+    public function testFormatRoundingModeHalfDown($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFDOWN);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFDOWN rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundHalfDownProvider()
+    {
+        return [
+            [1.121, '1.12'],
+            [1.123, '1.12'],
+            [1.125, '1.12'],
+            [1.127, '1.13'],
+            [1.129, '1.13'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundHalfEvenProvider
+     */
+    public function testFormatRoundingModeHalfEven($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_HALFEVEN);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_HALFEVEN rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundHalfEvenProvider()
+    {
+        return [
+            [1.121, '1.12'],
+            [1.123, '1.12'],
+            [1.125, '1.12'],
+            [1.127, '1.13'],
+            [1.129, '1.13'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundCeilingProvider
+     */
+    public function testFormatRoundingModeCeiling($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_CEILING);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_CEILING rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundCeilingProvider()
+    {
+        return [
+            [1.123, '1.13'],
+            [1.125, '1.13'],
+            [1.127, '1.13'],
+            [-1.123, '-1.12'],
+            [-1.125, '-1.12'],
+            [-1.127, '-1.12'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundFloorProvider
+     */
+    public function testFormatRoundingModeFloor($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_FLOOR);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_FLOOR rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundFloorProvider()
+    {
+        return [
+            [1.123, '1.12'],
+            [1.125, '1.12'],
+            [1.127, '1.12'],
+            [-1.123, '-1.13'],
+            [-1.125, '-1.13'],
+            [-1.127, '-1.13'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundDownProvider
+     */
+    public function testFormatRoundingModeDown($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_DOWN);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_DOWN rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundDownProvider()
+    {
+        return [
+            [1.123, '1.12'],
+            [1.125, '1.12'],
+            [1.127, '1.12'],
+            [-1.123, '-1.12'],
+            [-1.125, '-1.12'],
+            [-1.127, '-1.12'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    /**
+     * @dataProvider formatRoundingModeRoundUpProvider
+     */
+    public function testFormatRoundingModeUp($value, $expected)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
+
+        $formatter->setAttribute(NumberFormatter::ROUNDING_MODE, NumberFormatter::ROUND_UP);
+        $this->assertSame($expected, $formatter->format($value), '->format() with ROUND_UP rounding mode.');
+    }
+
+    public static function formatRoundingModeRoundUpProvider()
+    {
+        return [
+            [1.123, '1.13'],
+            [1.125, '1.13'],
+            [1.127, '1.13'],
+            [-1.123, '-1.13'],
+            [-1.125, '-1.13'],
+            [-1.127, '-1.13'],
+            [1020 / 100, '10.20'],
+        ];
+    }
+
+    public function testGetLocale()
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $this->assertEquals('en', $formatter->getLocale());
+    }
+
+    public function testGetSymbol()
+    {
+        $decimalFormatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $currencyFormatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        $r = new \ReflectionProperty('Symfony\Polyfill\Intl\Icu\NumberFormatter', 'enSymbols');
+        $r->setAccessible(true);
+        $expected = $r->getValue();
+
+        for ($i = 0; $i <= 17; ++$i) {
+            $this->assertSame($expected[1][$i], $decimalFormatter->getSymbol($i));
+            $this->assertSame($expected[2][$i], $currencyFormatter->getSymbol($i));
+        }
+    }
+
+    public function testGetTextAttribute()
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '63.1', '<')) {
+            $this->markTestSkipped('ICU version 63.1 is required.');
+        }
+
+        $decimalFormatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $currencyFormatter = static::getNumberFormatter('en', NumberFormatter::CURRENCY);
+
+        $r = new \ReflectionProperty('Symfony\Polyfill\Intl\Icu\NumberFormatter', 'enTextAttributes');
+        $r->setAccessible(true);
+        $expected = $r->getValue();
+
+        for ($i = 0; $i <= 5; ++$i) {
+            $this->assertSame($expected[1][$i], $decimalFormatter->getTextAttribute($i));
+            $this->assertSame($expected[2][$i], $currencyFormatter->getTextAttribute($i));
+        }
+    }
+
+    /**
+     * @dataProvider parseProvider
+     */
+    public function testParse($value, $expected, $message, $expectedPosition, $groupingUsed = true)
+    {
+        if (!\defined('INTL_ICU_VERSION') || version_compare(\INTL_ICU_VERSION, '62.1', '<')) {
+            $this->markTestSkipped('ICU version 62.1 is required.');
+        }
+
+        $position = 0;
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->setAttribute(NumberFormatter::GROUPING_USED, $groupingUsed);
+        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_DOUBLE, $position);
+        $this->assertSame($expected, $parsedValue, $message);
+        $this->assertSame($expectedPosition, $position, $message);
+
+        if (false === $expected) {
+            $errorCode = Icu::U_PARSE_ERROR;
+            $errorMessage = 'Number parsing failed: U_PARSE_ERROR';
+        } else {
+            $errorCode = Icu::U_ZERO_ERROR;
+            $errorMessage = 'U_ZERO_ERROR';
+        }
+
+        $this->assertSame($errorMessage, static::getIntlErrorMessage());
+        $this->assertSame($errorCode, static::getIntlErrorCode());
+        $this->assertSame(0 !== $errorCode, static::isIntlFailure(static::getIntlErrorCode()));
+        $this->assertSame($errorMessage, $formatter->getErrorMessage());
+        $this->assertSame($errorCode, $formatter->getErrorCode());
+        $this->assertSame(0 !== $errorCode, static::isIntlFailure($formatter->getErrorCode()));
+    }
+
+    public static function parseProvider()
+    {
+        return [
+            ['prefix1', false, '->parse() does not parse a number with a string prefix.', 0],
+            ['prefix1', false, '->parse() does not parse a number with a string prefix.', 0, false],
+            ['1.4suffix', (float) 1.4, '->parse() parses a number with a string suffix.', 3],
+            ['1.4suffix', (float) 1.4, '->parse() parses a number with a string suffix.', 3, false],
+            ['1,234.4suffix', 1234.4, '->parse() parses a number with a string suffix.', 7],
+            ['1,234.4suffix', 1.0, '->parse() parses a number with a string suffix.', 1, false],
+            ['-.4suffix', (float) -0.4, '->parse() parses a negative dot float with suffix.', 3],
+            ['-.4suffix', (float) -0.4, '->parse() parses a negative dot float with suffix.', 3, false],
+            [',4', false, '->parse() does not parse when invalid grouping used.', 0],
+            [',4', false, '->parse() does not parse when invalid grouping used.', 0, false],
+            ['123,4', false, '->parse() does not parse when invalid grouping used.', 0],
+            ['123,4', 123.0, '->parse() truncates invalid grouping when grouping is disabled.', 3, false],
+            ['123,a4', 123.0, '->parse() truncates a string suffix.', 3],
+            ['123,a4', 123.0, '->parse() truncates a string suffix.', 3, false],
+            ['-123,4', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123,4', -123.0, '->parse() truncates invalid grouping when grouping is disabled.', 4, false],
+            ['-123,4567', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123,4567', -123.0, '->parse() truncates invalid grouping when grouping is disabled.', 4, false],
+            ['-123,456,789', -123456789.0, '->parse() parses a number with grouping.', 12],
+            ['-123,456,789', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
+            ['-123,456,789.66', -123456789.66, '->parse() parses a number with grouping.', 15],
+            ['-123,456,789.66', -123.00, '->parse() truncates a group if grouping is disabled.', 4, false],
+            ['-123,456789.66', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123,456789.66', -123.00, '->parse() truncates a group if grouping is disabled.', 4, false],
+            ['-123456,789.66', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123456,789.66', -123456.00, '->parse() truncates a group if grouping is disabled.', 7, false],
+            ['-123,456,78', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123,456,78', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
+            ['-123,45,789', false, '->parse() does not parse when invalid grouping used.', 1],
+            ['-123,45,789', -123.0, '->parse() truncates a group if grouping is disabled.', 4, false],
+            ['-123,,456', -123.0, '->parse() parses when grouping is duplicated.', 4],
+            ['-123,,456', -123.0, '->parse() parses when grouping is disabled.', 4, false],
+            ['-123,,4', -123.0, '->parse() parses when grouping is duplicated.', 4],
+            ['-123,,4', -123.0, '->parse() parses when grouping is duplicated.', 4, false],
+            ['239.', 239.0, '->parse() parses when string ends with decimal separator.', 4],
+            ['239.', 239.0, '->parse() parses when string ends with decimal separator.', 4, false],
+        ];
+    }
+
+    public function testParseTypeDefault()
+    {
+        if (\PHP_VERSION_ID >= 80000) {
+            $this->expectException(\ValueError::class);
+        } elseif (method_exists($this, 'expectWarning')) {
+            $this->expectWarning();
+        } else {
+            $this->expectException(Warning::class);
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->parse('1', NumberFormatter::TYPE_DEFAULT);
+    }
+
+    /**
+     * @dataProvider parseTypeInt32Provider
+     */
+    public function testParseTypeInt32($value, $expected, $message = '')
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_INT32);
+        $this->assertSame($expected, $parsedValue, $message);
+    }
+
+    public static function parseTypeInt32Provider()
+    {
+        return [
+            ['1', 1],
+            ['1.1', 1],
+            ['.1', 0],
+            ['2,147,483,647', 2147483647],
+            ['-2,147,483,648', -2147483647 - 1],
+            ['2,147,483,648', false, '->parse() TYPE_INT32 returns false when the number is greater than the integer positive range.'],
+            ['-2,147,483,649', false, '->parse() TYPE_INT32 returns false when the number is greater than the integer negative range.'],
+        ];
+    }
+
+    public function testParseTypeInt64With32BitIntegerInPhp32Bit()
+    {
+        if (4 !== \PHP_INT_SIZE) {
+            $this->markTestSkipped('PHP 32 bit is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $parsedValue = $formatter->parse('2,147,483,647', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+        $this->assertEquals(2147483647, $parsedValue);
+
+        $parsedValue = $formatter->parse('-2,147,483,648', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+        $this->assertEquals(-2147483648, $parsedValue);
+    }
+
+    public function testParseTypeInt64With32BitIntegerInPhp64Bit()
+    {
+        if (8 !== \PHP_INT_SIZE) {
+            $this->markTestSkipped('PHP 64 bit is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $parsedValue = $formatter->parse('2,147,483,647', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+        $this->assertEquals(2147483647, $parsedValue);
+
+        $parsedValue = $formatter->parse('-2,147,483,648', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+        $this->assertEquals(-2147483647 - 1, $parsedValue);
+    }
+
+    /**
+     * If PHP is compiled in 32bit mode, the returned value for a 64bit integer are float numbers.
+     */
+    public function testParseTypeInt64With64BitIntegerInPhp32Bit()
+    {
+        if (4 !== \PHP_INT_SIZE) {
+            $this->markTestSkipped('PHP 32 bit is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        // int 64 using only 32 bit range strangeness
+        $parsedValue = $formatter->parse('2,147,483,648', NumberFormatter::TYPE_INT64);
+        $this->assertIsFloat($parsedValue);
+        $this->assertEquals(2147483648, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.');
+
+        $parsedValue = $formatter->parse('-2,147,483,649', NumberFormatter::TYPE_INT64);
+        $this->assertIsFloat($parsedValue);
+        $this->assertEquals(-2147483649, $parsedValue, '->parse() TYPE_INT64 does not use true 64 bit integers, using only the 32 bit range.');
+    }
+
+    /**
+     * If PHP is compiled in 64bit mode, the returned value for a 64bit integer are 32bit integer numbers.
+     */
+    public function testParseTypeInt64With64BitIntegerInPhp64Bit()
+    {
+        if (8 !== \PHP_INT_SIZE) {
+            $this->markTestSkipped('PHP 64 bit is required.');
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+
+        $parsedValue = $formatter->parse('2,147,483,648', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+
+        $this->assertEquals(2147483648, $parsedValue, '->parse() TYPE_INT64 uses true 64 bit integers (PHP >= 5.3.14 and PHP >= 5.4.4).');
+
+        $parsedValue = $formatter->parse('-2,147,483,649', NumberFormatter::TYPE_INT64);
+        $this->assertIsInt($parsedValue);
+
+        $this->assertEquals(-2147483649, $parsedValue, '->parse() TYPE_INT64 uses true 64 bit integers (PHP >= 5.3.14 and PHP >= 5.4.4).');
+    }
+
+    /**
+     * @dataProvider parseTypeDoubleProvider
+     */
+    public function testParseTypeDouble($value, $expectedValue)
+    {
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $parsedValue = $formatter->parse($value, NumberFormatter::TYPE_DOUBLE);
+        $this->assertEqualsWithDelta($expectedValue, $parsedValue, 0.001);
+    }
+
+    public static function parseTypeDoubleProvider()
+    {
+        return [
+            ['1', (float) 1],
+            ['1.1', 1.1],
+            ['9,223,372,036,854,775,808', 9223372036854775808],
+            ['-9,223,372,036,854,775,809', -9223372036854775809],
+        ];
+    }
+
+    public function testParseTypeCurrency()
+    {
+        if (\PHP_VERSION_ID >= 80000) {
+            $this->expectException(\ValueError::class);
+        } elseif (method_exists($this, 'expectWarning')) {
+            $this->expectWarning();
+        } else {
+            $this->expectException(Warning::class);
+        }
+
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->parse('1', NumberFormatter::TYPE_CURRENCY);
+    }
+
+    public function testParseWithNotNullPositionValue()
+    {
+        $position = 1;
+        $formatter = static::getNumberFormatter('en', NumberFormatter::DECIMAL);
+        $formatter->parse('123', NumberFormatter::TYPE_DOUBLE, $position);
+        $this->assertEquals(3, $position);
+    }
+
+    /**
+     * @return NumberFormatter|\NumberFormatter
+     */
+    abstract protected static function getNumberFormatter(string $locale = 'en', ?string $style = null, ?string $pattern = null);
+
+    abstract protected static function getIntlErrorMessage(): string;
+
+    abstract protected static function getIntlErrorCode(): int;
+
+    /**
+     * @param int $errorCode
+     */
+    abstract protected static function isIntlFailure($errorCode): bool;
+}
diff --git a/tests/Intl/Icu/CollatorTest.php b/tests/Intl/Icu/CollatorTest.php
index dcb6a87..aadb20e 100644
--- a/tests/Intl/Icu/CollatorTest.php
+++ b/tests/Intl/Icu/CollatorTest.php
@@ -15,11 +15,12 @@ use Symfony\Polyfill\Intl\Icu\Collator;
 use Symfony\Polyfill\Intl\Icu\Exception\MethodArgumentValueNotImplementedException;
 use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
 use Symfony\Polyfill\Intl\Icu\Icu;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractCollatorTestCase;
 
 /**
  * @group class-polyfill
  */
-class CollatorTest extends AbstractCollatorTest
+class CollatorTest extends AbstractCollatorTestCase
 {
     public function testConstructorWithUnsupportedLocale()
     {
diff --git a/tests/Intl/Icu/IcuTest.php b/tests/Intl/Icu/IcuTest.php
index f1b4521..6195553 100644
--- a/tests/Intl/Icu/IcuTest.php
+++ b/tests/Intl/Icu/IcuTest.php
@@ -12,8 +12,9 @@
 namespace Symfony\Polyfill\Tests\Intl\Icu;
 
 use Symfony\Polyfill\Intl\Icu\Icu;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractIcuTestCase;
 
-class IcuTest extends AbstractIcuTest
+class IcuTest extends AbstractIcuTestCase
 {
     protected function getIntlErrorName($errorCode)
     {
diff --git a/tests/Intl/Icu/IntlDateFormatterTest.php b/tests/Intl/Icu/IntlDateFormatterTest.php
index 6adc5e7..f99d7c6 100644
--- a/tests/Intl/Icu/IntlDateFormatterTest.php
+++ b/tests/Intl/Icu/IntlDateFormatterTest.php
@@ -17,12 +17,13 @@ use Symfony\Polyfill\Intl\Icu\Exception\MethodNotImplementedException;
 use Symfony\Polyfill\Intl\Icu\Exception\NotImplementedException;
 use Symfony\Polyfill\Intl\Icu\Icu;
 use Symfony\Polyfill\Intl\Icu\IntlDateFormatter;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractIntlDateFormatterTestCase;
 
 /**
  * @group class-polyfill
  * @group time-sensitive
  */
-class IntlDateFormatterTest extends AbstractIntlDateFormatterTest
+class IntlDateFormatterTest extends AbstractIntlDateFormatterTestCase
 {
     public function testConstructor()
     {
diff --git a/tests/Intl/Icu/LocaleTest.php b/tests/Intl/Icu/LocaleTest.php
index 85896db..edeac3a 100644
--- a/tests/Intl/Icu/LocaleTest.php
+++ b/tests/Intl/Icu/LocaleTest.php
@@ -17,7 +17,7 @@ use Symfony\Polyfill\Intl\Icu\Locale;
 /**
  * @group class-polyfill
  */
-class LocaleTest extends AbstractLocaleTest
+class LocaleTest extends AbstractLocaleTestCase
 {
     public function testAcceptFromHttp()
     {
diff --git a/tests/Intl/Icu/NumberFormatterTest.php b/tests/Intl/Icu/NumberFormatterTest.php
index 8b6dd92..0e2f458 100644
--- a/tests/Intl/Icu/NumberFormatterTest.php
+++ b/tests/Intl/Icu/NumberFormatterTest.php
@@ -24,7 +24,7 @@ use Symfony\Polyfill\Intl\Icu\NumberFormatter;
  *
  * @group class-polyfill
  */
-class NumberFormatterTest extends AbstractNumberFormatterTest
+class NumberFormatterTest extends AbstractNumberFormatterTestCase
 {
     public function testConstructorWithUnsupportedLocale()
     {
diff --git a/tests/Intl/Icu/Verification/CollatorTest.php b/tests/Intl/Icu/Verification/CollatorTest.php
index 834f133..7e38452 100644
--- a/tests/Intl/Icu/Verification/CollatorTest.php
+++ b/tests/Intl/Icu/Verification/CollatorTest.php
@@ -11,7 +11,7 @@
 
 namespace Symfony\Polyfill\Tests\Intl\Icu\Verification;
 
-use Symfony\Polyfill\Tests\Intl\Icu\AbstractCollatorTest;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractCollatorTestCase;
 
 /**
  * Verifies that {@link AbstractCollatorTest} matches the behavior of the
@@ -23,7 +23,7 @@ use Symfony\Polyfill\Tests\Intl\Icu\AbstractCollatorTest;
  *
  * @group class-polyfill
  */
-class CollatorTest extends AbstractCollatorTest
+class CollatorTest extends AbstractCollatorTestCase
 {
     protected function setUp(): void
     {
diff --git a/tests/Intl/Icu/Verification/IcuTest.php b/tests/Intl/Icu/Verification/IcuTest.php
index 5474adc..b48925a 100644
--- a/tests/Intl/Icu/Verification/IcuTest.php
+++ b/tests/Intl/Icu/Verification/IcuTest.php
@@ -11,7 +11,7 @@
 
 namespace Symfony\Polyfill\Tests\Intl\Icu\Verification;
 
-use Symfony\Polyfill\Tests\Intl\Icu\AbstractIcuTest;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractIcuTestCase;
 
 /**
  * Verifies that {@link AbstractIcuTest} matches the behavior of the
@@ -23,7 +23,7 @@ use Symfony\Polyfill\Tests\Intl\Icu\AbstractIcuTest;
  *
  * @group class-polyfill
  */
-class IcuTest extends AbstractIcuTest
+class IcuTest extends AbstractIcuTestCase
 {
     protected function setUp(): void
     {
diff --git a/tests/Intl/Icu/Verification/IntlDateFormatterTest.php b/tests/Intl/Icu/Verification/IntlDateFormatterTest.php
index da88e32..ff7cbc8 100644
--- a/tests/Intl/Icu/Verification/IntlDateFormatterTest.php
+++ b/tests/Intl/Icu/Verification/IntlDateFormatterTest.php
@@ -12,7 +12,7 @@
 namespace Symfony\Polyfill\Tests\Intl\Icu\Verification;
 
 use Symfony\Polyfill\Intl\Icu\IntlDateFormatter;
-use Symfony\Polyfill\Tests\Intl\Icu\AbstractIntlDateFormatterTest;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractIntlDateFormatterTestCase;
 
 /**
  * Verifies that {@link AbstractIntlDateFormatterTest} matches the behavior of
@@ -24,7 +24,7 @@ use Symfony\Polyfill\Tests\Intl\Icu\AbstractIntlDateFormatterTest;
  *
  * @group class-polyfill
  */
-class IntlDateFormatterTest extends AbstractIntlDateFormatterTest
+class IntlDateFormatterTest extends AbstractIntlDateFormatterTestCase
 {
     /**
      * @dataProvider formatProvider
diff --git a/tests/Intl/Icu/Verification/LocaleTest.php b/tests/Intl/Icu/Verification/LocaleTest.php
index 6d9c35e..41059a2 100644
--- a/tests/Intl/Icu/Verification/LocaleTest.php
+++ b/tests/Intl/Icu/Verification/LocaleTest.php
@@ -11,7 +11,7 @@
 
 namespace Symfony\Polyfill\Tests\Intl\Icu\Verification;
 
-use Symfony\Polyfill\Tests\Intl\Icu\AbstractLocaleTest;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractLocaleTestCase;
 
 /**
  * Verifies that {@link AbstractLocaleTest} matches the behavior of the
@@ -23,7 +23,7 @@ use Symfony\Polyfill\Tests\Intl\Icu\AbstractLocaleTest;
  *
  * @group class-polyfill
  */
-class LocaleTest extends AbstractLocaleTest
+class LocaleTest extends AbstractLocaleTestCase
 {
     protected function setUp(): void
     {
diff --git a/tests/Intl/Icu/Verification/NumberFormatterTest.php b/tests/Intl/Icu/Verification/NumberFormatterTest.php
index 38afae2..4d7e0d7 100644
--- a/tests/Intl/Icu/Verification/NumberFormatterTest.php
+++ b/tests/Intl/Icu/Verification/NumberFormatterTest.php
@@ -11,7 +11,7 @@
 
 namespace Symfony\Polyfill\Tests\Intl\Icu\Verification;
 
-use Symfony\Polyfill\Tests\Intl\Icu\AbstractNumberFormatterTest;
+use Symfony\Polyfill\Tests\Intl\Icu\AbstractNumberFormatterTestCase;
 
 /**
  * Note that there are some values written like -2147483647 - 1. This is the lower 32bit int max and is a known
@@ -21,7 +21,7 @@ use Symfony\Polyfill\Tests\Intl\Icu\AbstractNumberFormatterTest;
  *
  * @group class-polyfill
  */
-class NumberFormatterTest extends AbstractNumberFormatterTest
+class NumberFormatterTest extends AbstractNumberFormatterTestCase
 {
     protected function setUp(): void
     {
