Skip to content

Commit 2d1f62a

Browse files
[Clock] Add TimePoint: an immutable DateTime implementation with stricter error handling and return types
1 parent 3265ec2 commit 2d1f62a

File tree

12 files changed

+239
-41
lines changed

12 files changed

+239
-41
lines changed

.github/expected-missing-return-types.diff

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,16 @@ diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/
15121512
+ public function __wakeup(): void
15131513
{
15141514
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
1515+
diff --git a/src/Symfony/Component/Clock/ClockAwareTrait.php b/src/Symfony/Component/Clock/ClockAwareTrait.php
1516+
--- a/src/Symfony/Component/Clock/ClockAwareTrait.php
1517+
+++ b/src/Symfony/Component/Clock/ClockAwareTrait.php
1518+
@@ -33,5 +33,5 @@ trait ClockAwareTrait
1519+
* @return TimePoint
1520+
*/
1521+
- protected function now(): \DateTimeImmutable
1522+
+ protected function now(): TimePoint
1523+
{
1524+
$now = ($this->clock ??= new Clock())->now();
15151525
diff --git a/src/Symfony/Component/Config/ConfigCacheInterface.php b/src/Symfony/Component/Config/ConfigCacheInterface.php
15161526
--- a/src/Symfony/Component/Config/ConfigCacheInterface.php
15171527
+++ b/src/Symfony/Component/Config/ConfigCacheInterface.php

src/Symfony/Component/Clock/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.4
55
---
66

7+
* Add `TimePoint`: an immutable DateTime implementation with stricter error handling and return types
78
* Throw `DateMalformedStringException`/`DateInvalidTimeZoneException` when appropriate
89
* Add `$modifier` argument to the `now()` helper
910

src/Symfony/Component/Clock/Clock.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ public static function set(PsrClockInterface $clock): void
4444
self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock);
4545
}
4646

47-
public function now(): \DateTimeImmutable
47+
public function now(): TimePoint
4848
{
4949
$now = ($this->clock ?? self::get())->now();
5050

51+
if (!$now instanceof TimePoint) {
52+
$now = TimePoint::createFromInterface($now);
53+
}
54+
5155
return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now;
5256
}
5357

src/Symfony/Component/Clock/ClockAwareTrait.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ public function setClock(ClockInterface $clock): void
2929
$this->clock = $clock;
3030
}
3131

32+
/**
33+
* @return TimePoint
34+
*/
3235
protected function now(): \DateTimeImmutable
3336
{
34-
return ($this->clock ??= new Clock())->now();
37+
$now = ($this->clock ??= new Clock())->now();
38+
39+
return $now instanceof TimePoint ? $now : Moment::createFromInterface($now);
3540
}
3641
}

src/Symfony/Component/Clock/MockClock.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
final class MockClock implements ClockInterface
2222
{
23-
private \DateTimeImmutable $now;
23+
private TimePoint $now;
2424

2525
/**
2626
* @throws \DateMalformedStringException When $now is invalid
@@ -38,20 +38,16 @@ public function __construct(\DateTimeImmutable|string $now = 'now', \DateTimeZon
3838
}
3939
}
4040

41-
if (\PHP_VERSION_ID >= 80300 && \is_string($now)) {
42-
$now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC'));
43-
} elseif (\is_string($now)) {
44-
try {
45-
$now = new \DateTimeImmutable($now, $timezone ?? new \DateTimeZone('UTC'));
46-
} catch (\Exception $e) {
47-
throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e);
48-
}
41+
if (\is_string($now)) {
42+
$now = new TimePoint($now, $timezone ?? new \DateTimeZone('UTC'));
43+
} elseif (!$now instanceof TimePoint) {
44+
$now = TimePoint::createFromInterface($now);
4945
}
5046

5147
$this->now = null !== $timezone ? $now->setTimezone($timezone) : $now;
5248
}
5349

54-
public function now(): \DateTimeImmutable
50+
public function now(): TimePoint
5551
{
5652
return clone $this->now;
5753
}
@@ -62,7 +58,7 @@ public function sleep(float|int $seconds): void
6258
$now = substr_replace(sprintf('@%07.0F', $now), '.', -6, 0);
6359
$timezone = $this->now->getTimezone();
6460

65-
$this->now = (new \DateTimeImmutable($now, $timezone))->setTimezone($timezone);
61+
$this->now = TimePoint::createFromInterface(new \DateTimeImmutable($now, $timezone))->setTimezone($timezone);
6662
}
6763

6864
/**

src/Symfony/Component/Clock/MonotonicClock.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(\DateTimeZone|string $timezone = null)
3838
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
3939
}
4040

41-
public function now(): \DateTimeImmutable
41+
public function now(): TimePoint
4242
{
4343
[$s, $us] = hrtime();
4444

@@ -56,7 +56,7 @@ public function now(): \DateTimeImmutable
5656

5757
$now = '@'.($s + $this->sOffset).'.'.$now;
5858

59-
return (new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
59+
return TimePoint::createFromInterface(new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
6060
}
6161

6262
public function sleep(float|int $seconds): void

src/Symfony/Component/Clock/NativeClock.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public function __construct(\DateTimeZone|string $timezone = null)
2828
$this->timezone = \is_string($timezone ??= date_default_timezone_get()) ? $this->withTimeZone($timezone)->timezone : $timezone;
2929
}
3030

31-
public function now(): \DateTimeImmutable
31+
public function now(): TimePoint
3232
{
33-
return new \DateTimeImmutable('now', $this->timezone);
33+
return TimePoint::createFromInterface(new \DateTimeImmutable('now', $this->timezone));
3434
}
3535

3636
public function sleep(float|int $seconds): void

src/Symfony/Component/Clock/Resources/now.php

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,14 @@
1515
/**
1616
* @throws \DateMalformedStringException When the modifier is invalid
1717
*/
18-
function now(string $modifier = null): \DateTimeImmutable
18+
function now(string $modifier = 'now'): TimePoint
1919
{
20-
if (null === $modifier || 'now' === $modifier) {
21-
return Clock::get()->now();
20+
if ('now' !== $modifier) {
21+
return new TimePoint($modifier);
2222
}
2323

2424
$now = Clock::get()->now();
2525

26-
if (\PHP_VERSION_ID < 80300) {
27-
try {
28-
$tz = (new \DateTimeImmutable($modifier, $now->getTimezone()))->getTimezone();
29-
} catch (\Exception $e) {
30-
throw new \DateMalformedStringException($e->getMessage(), $e->getCode(), $e);
31-
}
32-
$now = $now->setTimezone($tz);
33-
34-
return @$now->modify($modifier) ?: throw new \DateMalformedStringException(error_get_last()['message'] ?? sprintf('Invalid date modifier "%s".', $modifier));
35-
}
36-
37-
$tz = (new \DateTimeImmutable($modifier, $now->getTimezone()))->getTimezone();
38-
39-
return $now->setTimezone($tz)->modify($modifier);
26+
return $now instanceof TimePoint ? $now : Moment::createFromInterface($now);
4027
}
4128
}

src/Symfony/Component/Clock/Tests/ClockAwareTraitTest.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Clock\ClockAwareTrait;
1616
use Symfony\Component\Clock\MockClock;
17+
use Symfony\Component\Clock\TimePoint;
1718

1819
class ClockAwareTraitTest extends TestCase
1920
{
2021
public function testTrait()
2122
{
22-
$sut = new class() {
23-
use ClockAwareTrait {
24-
now as public;
25-
}
26-
};
23+
$sut = new ClockAwareTestImplem();
2724

28-
$this->assertInstanceOf(\DateTimeImmutable::class, $sut->now());
25+
$this->assertInstanceOf(TimePoint::class, $sut->now());
2926

3027
$clock = new MockClock();
3128
$sut = new $sut();
@@ -38,3 +35,10 @@ public function testTrait()
3835
$this->assertSame(1.0, round($sut->now()->getTimestamp() - $ts, 1));
3936
}
4037
}
38+
39+
class ClockAwareTestImplem
40+
{
41+
use ClockAwareTrait {
42+
now as public;
43+
}
44+
}

src/Symfony/Component/Clock/Tests/ClockTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Clock\MockClock;
1818
use Symfony\Component\Clock\NativeClock;
1919
use Symfony\Component\Clock\Test\ClockSensitiveTrait;
20+
use Symfony\Component\Clock\TimePoint;
2021

2122
use function Symfony\Component\Clock\now;
2223

@@ -35,7 +36,7 @@ public function testMockClock()
3536

3637
public function testNativeClock()
3738
{
38-
$this->assertInstanceOf(\DateTimeImmutable::class, now());
39+
$this->assertInstanceOf(TimePoint::class, now());
3940
$this->assertInstanceOf(NativeClock::class, Clock::get());
4041
}
4142

0 commit comments

Comments
 (0)