From 9e48e0d1e1e65005edbb31d99e34472c40a7d9d8 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 30 Mar 2024 17:15:46 +0100 Subject: [PATCH] [Clock] Polyfill DateTimeImmutable::createFromTimestamp() --- src/Symfony/Component/Clock/DatePoint.php | 21 ++++++++++ .../Component/Clock/Tests/DatePointTest.php | 41 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/Symfony/Component/Clock/DatePoint.php b/src/Symfony/Component/Clock/DatePoint.php index 26016fea2f216..f96c816d2405c 100644 --- a/src/Symfony/Component/Clock/DatePoint.php +++ b/src/Symfony/Component/Clock/DatePoint.php @@ -66,6 +66,27 @@ public static function createFromMutable(\DateTime $object): static return parent::createFromMutable($object); } + public static function createFromTimestamp(int|float $timestamp): static + { + if (\PHP_VERSION_ID >= 80400) { + return parent::createFromTimestamp($timestamp); + } + + if (\is_int($timestamp) || !$ms = (int) $timestamp - $timestamp) { + return static::createFromFormat('U', (string) $timestamp); + } + + if (!is_finite($timestamp) || \PHP_INT_MAX + 1.0 <= $timestamp || \PHP_INT_MIN > $timestamp) { + throw new \DateRangeError(sprintf('DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between %s and %s.999999, %s given', \PHP_INT_MIN, \PHP_INT_MAX, $timestamp)); + } + + if ($timestamp < 0) { + $timestamp = (int) $timestamp - 2.0 + $ms; + } + + return static::createFromFormat('U.u', sprintf('%.6F', $timestamp)); + } + public function add(\DateInterval $interval): static { return parent::add($interval); diff --git a/src/Symfony/Component/Clock/Tests/DatePointTest.php b/src/Symfony/Component/Clock/Tests/DatePointTest.php index 5f7d579a4890c..5c38125281607 100644 --- a/src/Symfony/Component/Clock/Tests/DatePointTest.php +++ b/src/Symfony/Component/Clock/Tests/DatePointTest.php @@ -45,6 +45,47 @@ public function testCreateFromFormat() DatePoint::createFromFormat('Y-m-d H:i:s', 'Bad Date'); } + /** + * @dataProvider provideValidTimestamps + */ + public function testCreateFromTimestamp(int|float $timestamp, string $expected) + { + $date = DatePoint::createFromTimestamp($timestamp); + + $this->assertInstanceOf(DatePoint::class, $date); + $this->assertSame($expected, $date->format('Y-m-d\TH:i:s.uP')); + } + + public static function provideValidTimestamps(): iterable + { + yield 'positive integer' => [1359188516, '2013-01-26T08:21:56.000000+00:00']; + yield 'positive float' => [1359188516.123456, '2013-01-26T08:21:56.123456+00:00']; + yield 'positive integer-ish float' => [1359188516.0, '2013-01-26T08:21:56.000000+00:00']; + yield 'zero as integer' => [0, '1970-01-01T00:00:00.000000+00:00']; + yield 'zero as float' => [0.0, '1970-01-01T00:00:00.000000+00:00']; + yield 'negative integer' => [-100, '1969-12-31T23:58:20.000000+00:00']; + yield 'negative float' => [-100.123456, '1969-12-31T23:58:19.876544+00:00']; + yield 'negative integer-ish float' => [-100.0, '1969-12-31T23:58:20.000000+00:00']; + } + + /** + * @dataProvider provideOutOfRangeFloatTimestamps + */ + public function testCreateFromTimestampWithFloatOutOfRange(float $timestamp) + { + $this->expectException(\DateRangeError::class); + $this->expectExceptionMessage('DateTimeImmutable::createFromTimestamp(): Argument #1 ($timestamp) must be a finite number between'); + DatePoint::createFromTimestamp($timestamp); + } + + public static function provideOutOfRangeFloatTimestamps(): iterable + { + yield 'too large (positive)' => [1e20]; + yield 'too large (negative)' => [-1e20]; + yield 'NaN' => [\NAN]; + yield 'infinity' => [\INF]; + } + public function testModify() { $date = new DatePoint('2010-01-28 15:00:00');