diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php index 01c4647013..a4c71818e8 100644 --- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php +++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php @@ -200,8 +200,21 @@ public static function createFromTimestamp($timestamp, $timezone = NULL, $settin if (!is_numeric($timestamp)) { throw new \InvalidArgumentException('The timestamp must be numeric.'); } - $datetime = new static('', $timezone, $settings); - $datetime->setTimestamp($timestamp); + + // Because of an upstream bug, @see https://bugs.php.net/bug.php?id=77103, + // we have to use numeric Unix timestamp. However, when you create a + // \DateTime using the '@' format, the $timezone parameter is ignore. So, + // we have to explicitly set the time zone afterwards. + $datetime = new static('@' . $timestamp, NULL, $settings); + if ($timezone !== NULL) { + if (is_string($timezone)) { + $datetime->setTimezone(new \DateTimeZone($timezone)); + } + else { + $datetime->setTimezone($timezone); + } + } + return $datetime; } diff --git a/core/tests/Drupal/KernelTests/Core/Datetime/DateFormatterTest.php b/core/tests/Drupal/KernelTests/Core/Datetime/DateFormatterTest.php index 8f87a6de9b..be340d9d6b 100644 --- a/core/tests/Drupal/KernelTests/Core/Datetime/DateFormatterTest.php +++ b/core/tests/Drupal/KernelTests/Core/Datetime/DateFormatterTest.php @@ -110,4 +110,26 @@ public function testFormat() { $this->assertSame('<em>2007</em>', $formatter->format($timestamp, 'custom', '\<\e\m\>Y\<\/\e\m\>'), 'Em tags are not removed from dates.'); } + /** + * Tests DateFormatter::format() when DST. + * + * @covers ::format + */ + public function testFormatDST() { + // The 'America/Vancouver' time zone changed from DST to standard time at + // 2:00 AM, Nov 1, 2020 (the GMT offset is changed to UTC/GMT -8 hours). + $time = strtotime('2020-11-01T06:15:00+00:00'); + $timezone = "America/Vancouver"; + $date_formatter = \Drupal::service('date.formatter'); + for ($i = 0; $i < 5; $i++) { + $timestamp = $time + $i * 3600; + $date = new \DateTime(); + $date->setTimestamp($timestamp); + $date->setTimezone(new \DateTimeZone($timezone)); + + $this->assertEquals($date->format('c'), $date_formatter->format($timestamp, 'custom', 'c', $timezone)); + $this->assertEquals($date->format('I'), $date_formatter->format($timestamp, 'custom', 'I', $timezone)); + } + } + } diff --git a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php index cde8dacad8..3070e6eb8b 100644 --- a/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php +++ b/core/tests/Drupal/Tests/Component/Datetime/DateTimePlusTest.php @@ -564,6 +564,24 @@ public function providerTestTimestamp() { 'expected_offset' => 0, ], ], + // Create a date using the timestamp during a DST transition + [ + 'input' => 1604222100, + 'initial' => [ + 'timezone' => 'America/Vancouver', + 'format' => 'c I', + 'expected_date' => '2020-11-01T01:15:00-08:00 0', + 'expected_timezone' => 'America/Vancouver', + 'expected_offset' => '-28800', + ], + 'transform' => [ + 'timezone' => 'UTC', + 'format' => 'c', + 'expected_date' => '2020-11-01T09:15:00+00:00', + 'expected_timezone' => 'UTC', + 'expected_offset' => 0, + ], + ], ]; }