diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index adaeed91c254d..88c32b2d32184 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -1512,6 +1512,16 @@ diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/ + public function __wakeup(): void { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); +diff --git a/src/Symfony/Component/Clock/ClockAwareTrait.php b/src/Symfony/Component/Clock/ClockAwareTrait.php +--- a/src/Symfony/Component/Clock/ClockAwareTrait.php ++++ b/src/Symfony/Component/Clock/ClockAwareTrait.php +@@ -33,5 +33,5 @@ trait ClockAwareTrait + * @return DatePoint + */ +- protected function now(): \DateTimeImmutable ++ protected function now(): DatePoint + { + $now = ($this->clock ??= new Clock())->now(); diff --git a/src/Symfony/Component/Config/ConfigCacheInterface.php b/src/Symfony/Component/Config/ConfigCacheInterface.php --- a/src/Symfony/Component/Config/ConfigCacheInterface.php +++ b/src/Symfony/Component/Config/ConfigCacheInterface.php diff --git a/src/Symfony/Component/Clock/CHANGELOG.md b/src/Symfony/Component/Clock/CHANGELOG.md index 254e71c794b5e..3b13157397f0f 100644 --- a/src/Symfony/Component/Clock/CHANGELOG.md +++ b/src/Symfony/Component/Clock/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 6.4 --- + * Add `DatePoint`: an immutable DateTime implementation with stricter error handling and return types * Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate * Add `$modifier` argument to the `now()` helper diff --git a/src/Symfony/Component/Clock/Clock.php b/src/Symfony/Component/Clock/Clock.php index a738eb0b31fb0..311e8fc07abd0 100644 --- a/src/Symfony/Component/Clock/Clock.php +++ b/src/Symfony/Component/Clock/Clock.php @@ -44,10 +44,14 @@ public static function set(PsrClockInterface $clock): void self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock); } - public function now(): \DateTimeImmutable + public function now(): DatePoint { $now = ($this->clock ?? self::get())->now(); + if (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); + } + return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now; } diff --git a/src/Symfony/Component/Clock/ClockAwareTrait.php b/src/Symfony/Component/Clock/ClockAwareTrait.php index 02698d7fb222f..44ce044648894 100644 --- a/src/Symfony/Component/Clock/ClockAwareTrait.php +++ b/src/Symfony/Component/Clock/ClockAwareTrait.php @@ -29,8 +29,13 @@ public function setClock(ClockInterface $clock): void $this->clock = $clock; } + /** + * @return DatePoint + */ protected function now(): \DateTimeImmutable { - return ($this->clock ??= new Clock())->now(); + $now = ($this->clock ??= new Clock())->now(); + + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); } } diff --git a/src/Symfony/Component/Clock/DatePoint.php b/src/Symfony/Component/Clock/DatePoint.php new file mode 100644 index 0000000000000..dec8c1b38a2c3 --- /dev/null +++ b/src/Symfony/Component/Clock/DatePoint.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock; + +/** + * An immmutable DateTime with stricter error handling and return types than the native one. + * + * @author Nicolas Grekas + */ +final class DatePoint extends \DateTimeImmutable +{ + /** + * @throws \DateMalformedStringException When $datetime is invalid + */ + public function __construct(string $datetime = 'now', \DateTimeZone $timezone = null, parent $reference = null) + { + $now = $reference ?? Clock::get()->now(); + + if ('now' !== $datetime) { + if (!$now instanceof static) { + $now = static::createFromInterface($now); + } + + if (\PHP_VERSION_ID < 80300) { + try { + $timezone = (new parent($datetime, $timezone ?? $now->getTimezone()))->getTimezone(); + } catch (\Exception $e) { + throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e); + } + } else { + $timezone = (new parent($datetime, $timezone ?? $now->getTimezone()))->getTimezone(); + } + + $now = $now->setTimezone($timezone)->modify($datetime); + } elseif (null !== $timezone) { + $now = $now->setTimezone($timezone); + } + + if (\PHP_VERSION_ID < 80200) { + $now = (array) $now; + $this->date = $now['date']; + $this->timezone_type = $now['timezone_type']; + $this->timezone = $now['timezone']; + $this->__wakeup(); + + return; + } + + $this->__unserialize((array) $now); + } + + /** + * @throws \DateMalformedStringException When $format or $datetime are invalid + */ + public static function createFromFormat(string $format, string $datetime, \DateTimeZone $timezone = null): static + { + return parent::createFromFormat($format, $datetime, $timezone) ?: throw new \DateMalformedStringException(static::getLastErrors()['errors'][0] ?? 'Invalid date string or format.'); + } + + public static function createFromInterface(\DateTimeInterface $object): static + { + return parent::createFromInterface($object); + } + + public static function createFromMutable(\DateTime $object): static + { + return parent::createFromMutable($object); + } + + public function add(\DateInterval $interval): static + { + return parent::add($interval); + } + + public function sub(\DateInterval $interval): static + { + return parent::sub($interval); + } + + /** + * @throws \DateMalformedStringException When $modifier is invalid + */ + public function modify(string $modifier): static + { + if (\PHP_VERSION_ID < 80300) { + return @parent::modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid modifier: "%s".', $modifier)); + } + + return parent::modify($modifier); + } + + public function setTimestamp(int $value): static + { + return parent::setTimestamp($value); + } + + public function setDate(int $year, int $month, int $day): static + { + return parent::setDate($year, $month, $day); + } + + public function setISODate(int $year, int $week, int $day = 1): static + { + return parent::setISODate($year, $week, $day); + } + + public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static + { + return parent::setTime($hour, $minute, $second, $microsecond); + } + + public function setTimezone(\DateTimeZone $timezone): static + { + return parent::setTimezone($timezone); + } + + public function getTimezone(): \DateTimeZone + { + return parent::getTimezone() ?: throw new \DateInvalidTimeZoneException('The DatePoint object has no timezone.'); + } +} diff --git a/src/Symfony/Component/Clock/MockClock.php b/src/Symfony/Component/Clock/MockClock.php index b5e4b2e8f5ed9..b742c4331e052 100644 --- a/src/Symfony/Component/Clock/MockClock.php +++ b/src/Symfony/Component/Clock/MockClock.php @@ -20,7 +20,7 @@ */ final class MockClock implements ClockInterface { - private \DateTimeImmutable $now; + private DatePoint $now; /** * @throws \DateMalformedStringException When $now is invalid @@ -38,20 +38,16 @@ public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZon } } - if (\PHP_VERSION_ID >= 80300 && \is_string($now)) { - $now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC')); - } elseif (\is_string($now)) { - try { - $now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC')); - } catch (\Exception $e) { - throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e); - } + if (\is_string($now)) { + $now = new DatePoint($now, $timezone ?? new \DateTimeZone('UTC')); + } elseif (!$now instanceof DatePoint) { + $now = DatePoint::createFromInterface($now); } $this->now = null !== $timezone ? $now->setTimezone($timezone) : $now; } - public function now(): \DateTimeImmutable + public function now(): DatePoint { return clone $this->now; } @@ -62,7 +58,7 @@ public function sleep(float|int $seconds): void $now = substr_replace(sprintf('@%07.0F', $now), '.', -6, 0); $timezone = $this->now->getTimezone(); - $this->now = (new \DateTimeImmutable($now, $timezone))->setTimezone($timezone); + $this->now = DatePoint::createFromInterface(new \DateTimeImmutable($now, $timezone))->setTimezone($timezone); } /** diff --git a/src/Symfony/Component/Clock/MonotonicClock.php b/src/Symfony/Component/Clock/MonotonicClock.php index bf4d34ce706fd..a834dde1dbc56 100644 --- a/src/Symfony/Component/Clock/MonotonicClock.php +++ b/src/Symfony/Component/Clock/MonotonicClock.php @@ -38,7 +38,7 @@ public function __construct(\DateTimeZone|string $timezone = null) $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; } - public function now(): \DateTimeImmutable + public function now(): DatePoint { [$s, $us] = hrtime(); @@ -56,7 +56,7 @@ public function now(): \DateTimeImmutable $now = '@'.($s + $this->sOffset).'.'.$now; - return (new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone); + return DatePoint::createFromInterface(new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone); } public function sleep(float|int $seconds): void diff --git a/src/Symfony/Component/Clock/NativeClock.php b/src/Symfony/Component/Clock/NativeClock.php index 7d4fe36d46100..9480dae5f6957 100644 --- a/src/Symfony/Component/Clock/NativeClock.php +++ b/src/Symfony/Component/Clock/NativeClock.php @@ -28,9 +28,9 @@ public function __construct(\DateTimeZone|string $timezone = null) $this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone; } - public function now(): \DateTimeImmutable + public function now(): DatePoint { - return new \DateTimeImmutable('now', $this->timezone); + return DatePoint::createFromInterface(new \DateTimeImmutable('now', $this->timezone)); } public function sleep(float|int $seconds): void diff --git a/src/Symfony/Component/Clock/Resources/now.php b/src/Symfony/Component/Clock/Resources/now.php index d4999fd922ad5..47d086c67d11d 100644 --- a/src/Symfony/Component/Clock/Resources/now.php +++ b/src/Symfony/Component/Clock/Resources/now.php @@ -15,27 +15,14 @@ /** * @throws \DateMalformedStringException When the modifier is invalid */ - function now(string $modifier = null): \DateTimeImmutable + function now(string $modifier = 'now'): DatePoint { - if (null === $modifier || 'now' === $modifier) { - return Clock::get()->now(); + if ('now' !== $modifier) { + return new DatePoint($modifier); } $now = Clock::get()->now(); - if (\PHP_VERSION_ID < 80300) { - try { - $tz = (new \DateTimeImmutable($modifier, $now->getTimezone()))->getTimezone(); - } catch (\Exception $e) { - throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e); - } - $now = $now->setTimezone($tz); - - return @$now->modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid date modifier "%s".', $modifier)); - } - - $tz = (new \DateTimeImmutable($modifier, $now->getTimezone()))->getTimezone(); - - return $now->setTimezone($tz)->modify($modifier); + return $now instanceof DatePoint ? $now : DatePoint::createFromInterface($now); } } diff --git a/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php b/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php index c472541c64934..bb2cfceb78e9f 100644 --- a/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php +++ b/src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php @@ -13,19 +13,16 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Clock\ClockAwareTrait; +use Symfony\Component\Clock\DatePoint; use Symfony\Component\Clock\MockClock; class ClockAwareTraitTest extends TestCase { public function testTrait() { - $sut = new class() { - use ClockAwareTrait { - now as public; - } - }; + $sut = new ClockAwareTestImplem(); - $this->assertInstanceOf(\DateTimeImmutable::class, $sut->now()); + $this->assertInstanceOf(DatePoint::class, $sut->now()); $clock = new MockClock(); $sut = new $sut(); @@ -38,3 +35,10 @@ public function testTrait() $this->assertSame(1.0, round($sut->now()->getTimestamp() - $ts, 1)); } } + +class ClockAwareTestImplem +{ + use ClockAwareTrait { + now as public; + } +} diff --git a/src/Symfony/Component/Clock/Tests/ClockTest.php b/src/Symfony/Component/Clock/Tests/ClockTest.php index bf71543d3ce18..9b0b1a76ae405 100644 --- a/src/Symfony/Component/Clock/Tests/ClockTest.php +++ b/src/Symfony/Component/Clock/Tests/ClockTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Psr\Clock\ClockInterface; use Symfony\Component\Clock\Clock; +use Symfony\Component\Clock\DatePoint; use Symfony\Component\Clock\MockClock; use Symfony\Component\Clock\NativeClock; use Symfony\Component\Clock\Test\ClockSensitiveTrait; @@ -35,7 +36,7 @@ public function testMockClock() public function testNativeClock() { - $this->assertInstanceOf(\DateTimeImmutable::class, now()); + $this->assertInstanceOf(DatePoint::class, now()); $this->assertInstanceOf(NativeClock::class, Clock::get()); } diff --git a/src/Symfony/Component/Clock/Tests/DatePointTest.php b/src/Symfony/Component/Clock/Tests/DatePointTest.php new file mode 100644 index 0000000000000..4ebd0da7955c6 --- /dev/null +++ b/src/Symfony/Component/Clock/Tests/DatePointTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Clock\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\DatePoint; +use Symfony\Component\Clock\Test\ClockSensitiveTrait; + +class DatePointTest extends TestCase +{ + use ClockSensitiveTrait; + + public function testDatePoint() + { + self::mockTime('2010-01-28 15:00:00'); + + $date = new DatePoint(); + $this->assertSame('2010-01-28 15:00:00 UTC', $date->format('Y-m-d H:i:s e')); + + $date = new DatePoint('+1 day Europe/Paris'); + $this->assertSame('2010-01-29 16:00:00 Europe/Paris', $date->format('Y-m-d H:i:s e')); + + $date = new DatePoint('2022-01-28 15:00:00 Europe/Paris'); + $this->assertSame('2022-01-28 15:00:00 Europe/Paris', $date->format('Y-m-d H:i:s e')); + } + + public function testCreateFromFormat() + { + $date = DatePoint::createFromFormat('Y-m-d H:i:s', '2010-01-28 15:00:00'); + + $this->assertInstanceOf(DatePoint::class, $date); + $this->assertSame('2010-01-28 15:00:00', $date->format('Y-m-d H:i:s')); + + $this->expectException(\DateMalformedStringException::class); + $this->expectExceptionMessage('A four digit year could not be found'); + DatePoint::createFromFormat('Y-m-d H:i:s', 'Bad Date'); + } + + public function testModify() + { + $date = new DatePoint('2010-01-28 15:00:00'); + $date = $date->modify('+1 day'); + + $this->assertInstanceOf(DatePoint::class, $date); + $this->assertSame('2010-01-29 15:00:00', $date->format('Y-m-d H:i:s')); + + $this->expectException(\DateMalformedStringException::class); + $this->expectExceptionMessage('Failed to parse time string (Bad Date)'); + $date->modify('Bad Date'); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy