8000 [Clock] Add `TimePoint`: an immutable DateTime implementation with st… · symfony/symfony@0dc449a · GitHub
[go: up one dir, main page]

Skip to content

Commit 0dc449a

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

12 files changed

+239
-41
lines changed

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

+10
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 Instant
1520+
*/
1521+
- protected function now(): \DateTimeImmutable
1522+
+ protected function now(): Instant
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

+1
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

+5-1
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

+6-1
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

+7-11
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

+2-2
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

+2-2
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

+4-17
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

+10-6
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

+2-1
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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Clock\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Clock\Test\ClockSensitiveTrait;
16+
use Symfony\Component\Clock\TimePoint;
17+
18+
class TimePointTest extends TestCase
19+
{
20+
use ClockSensitiveTrait;
21+
22+
public function testTimePoint()
23+
{
24+
self::mockTime('2010-01-28 15:00:00');
25+
26+
$date = new TimePoint();
27+
$this->assertSame('2010-01-28 15:00:00 UTC', $date->format('Y-m-d H:i:s e'));
28+
29+
$date = new TimePoint('+1 day Europe/Paris');
30+
$this->assertSame('2010-01-29 16:00:00 Europe/Paris', $date->format('Y-m-d H:i:s e'));
31+
32+
$date = new TimePoint('2022-01-28 15:00:00 Europe/Paris');
33+
$this->assertSame('2022-01-28 15:00:00 Europe/Paris', $date->format('Y-m-d H:i:s e'));
34+
}
35+
36+
public function testCreateFromFormat()
37+
{
38+
$date = TimePoint::createFromFormat('Y-m-d H:i:s', '2010-01-28 15:00:00');
39+
40+
$this->assertInstanceOf(TimePoint::class, $date);
41+
$this->assertSame('2010-01-28 15:00:00', $date->format('Y-m-d H:i:s'));
42+
43+
$this->expectException(\DateMalformedStringException::class);
44+
$this->expectExceptionMessage('A four digit year could not be found');
45+
TimePoint::createFromFormat('Y-m-d H:i:s', 'Bad Date');
46+
}
47+
48+
public function testModify()
49+
{
50+
$date = new TimePoint('2010-01-28 15:00:00');
51+
$date = $date->modify('+1 day');
52+
53+
$this->assertInstanceOf(TimePoint::class, $date);
54+
$this->assertSame('2010-01-29 15:00:00', $date->format('Y-m-d H:i:s'));
55+
56+
$this->expectException(\DateMalformedStringException::class);
57+
$this->expectExceptionMessage('Failed to parse time string (Bad Date)');
58+
$date->modify('Bad Date');
59+
}
60+
}

0 commit comments

Comments
 (0)
0