Skip to content

Commit ab6b038

Browse files
[Clock] Add Clock class and now() function
1 parent bd6219d commit ab6b038

File tree

9 files changed

+248
-2
lines changed

9 files changed

+248
-2
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
},
188188
"autoload-dev": {
189189
"files": [
190+
"src/Symfony/Component/Clock/Resources/now.php",
190191
"src/Symfony/Component/VarDumper/Resources/functions/dump.php"
191192
]
192193
},

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
1616
use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer;
1717
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
18+
use Symfony\Component\Clock\Clock;
1819
use Symfony\Component\Clock\ClockInterface;
19-
use Symfony\Component\Clock\NativeClock;
2020
use Symfony\Component\Config\Loader\LoaderInterface;
2121
use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
2222
use Symfony\Component\Config\ResourceCheckerConfigCacheFactory;
@@ -229,7 +229,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : []
229229
->args([service(KernelInterface::class), service('logger')->nullOnInvalid()])
230230
->tag('kernel.cache_warmer')
231231

232-
->set('clock', NativeClock::class)
232+
->set('clock', Clock::class)
233233
->alias(ClockInterface::class, 'clock')
234234
->alias(PsrClockInterface::class, 'clock')
235235

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"phpdocumentor/type-resolver": "<1.4.0",
7777
"phpunit/phpunit": "<5.4.3",
7878
"symfony/asset": "<5.4",
79+
"symfony/clock": "<6.3",
7980
"symfony/console": "<5.4",
8081
"symfony/dotenv": "<5.4",
8182
"symfony/dom-crawler": "<5.4",

src/Symfony/Component/Clock/CHANGELOG.md

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

77
* Add `ClockAwareTrait` to help write time-sensitive classes
8+
* Add `Clock` class and `now()` function
89

910
6.2
1011
---

src/Symfony/Component/Clock/Clock.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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;
13+
14+
use Psr\Clock\ClockInterface as PsrClockInterface;
15+
16+
/**
17+
* A global clock.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
final class Clock implements ClockInterface
22+
{
23+
private static ClockInterface $globalClock;
24+
25+
public function __construct(
26+
private ?PsrClockInterface $clock = null,
27+
private ?\DateTimeZone $timezone = null,
28+
) {
29+
}
30+
31+
/**
32+
* Returns the current global clock.
33+
*
34+
* Note that you should prefer injecting a ClockInterface or using
35+
* ClockAwareTrait when possible instead of using this method.
36+
*/
37+
public static function get(): ClockInterface
38+
{
39+
return self::$globalClock ??= new NativeClock();
40+
}
41+
42+
public static function set(PsrClockInterface $clock): void
43+
{
44+
self::$globalClock = $clock instanceof ClockInterface ? $clock : new self($clock);
45+
}
46+
47+
public function now(): \DateTimeImmutable
48+
{
49+
$now = ($this->clock ?? self::$globalClock)->now();
50+
51+
return isset($this->timezone) ? $now->setTimezone($this->timezone) : $now;
52+
}
53+
54+
public function sleep(float|int $seconds): void
55+
{
56+
$clock = $this->clock ?? self::$globalClock;
57+
58+
if ($clock instanceof ClockInterface) {
59+
$clock->sleep($seconds);
60+
} else {
61+
(new NativeClock())->sleep($seconds);
62+
}
63+
}
64+
65+
public function withTimeZone(\DateTimeZone|string $timezone): static
66+
{
67+
$clone = clone $this;
68+
$clone->timezone = \is_string($timezone) ? new \DateTimeZone($timezone) : $timezone;
69+
70+
return $clone;
71+
}
72+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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;
13+
14+
/**
15+
* Returns the current time as a DateTimeImmutable.
16+
*
17+
* Note that you should prefer injecting a ClockInterface or using
18+
* ClockAwareTrait when possible instead of using this function.
19+
*/
20+
function now(): \DateTimeImmutable
21+
{
22+
return Clock::get()->now();
23+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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\Test;
13+
14+
use Psr\Clock\ClockInterface;
15+
use Symfony\Component\Clock\Clock;
16+
use Symfony\Component\Clock\MockClock;
17+
18+
use function Symfony\Component\Clock\now;
19+
20+
trait ClockSensitiveTrait
21+
{
22+
public static function mockTime(string|\DateTimeImmutable|ClockInterface|bool $when = true): ClockInterface
23+
{
24+
Clock::set(match (true) {
25+
false === $when => self::saveClockBeforeTest(false),
26+
true === $when => new MockClock(),
27+
$when instanceof ClockInterface => $when,
28+
$when instanceof \DateTimeImmutable => new MockClock($when),
29+
default => new MockClock(now()->modify($when)),
30+
});
31+
32+
return Clock::get();
33+
}
34+
35+
/**
36+
* @before
37+
*
38+
* @internal
39+
*/
40+
protected static function saveClockBeforeTest(bool $save = true): ClockInterface
41+
{
42+
static $originalClock;
43+
44+
return $save ? $originalClock = Clock::get() : $originalClock;
45+
}
46+
47+
/**
48+
* @after
49+
*
50+
* @internal
51+
*/
52+
protected static function restoreClockAfterTest(): void
53+
{
54+
Clock::set(self::saveClockBeforeTest(false));
55+
}
56+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 Psr\Clock\ClockInterface;
16+
use Symfony\Component\Clock\Clock;
17+
use Symfony\Component\Clock\MockClock;
18+
use Symfony\Component\Clock\NativeClock;
19+
use Symfony\Component\Clock\Test\ClockSensitiveTrait;
20+
21+
use function Symfony\Component\Clock\now;
22+
23+
class ClockTest extends TestCase
24+
{
25+
use ClockSensitiveTrait;
26+
27+
public function testMockClock()
28+
{
29+
$this->assertInstanceOf(NativeClock::class, Clock::get());
30+
31+
$clock = self::mockTime();
32+
$this->assertInstanceOf(MockClock::class, Clock::get());
33+
$this->assertSame(Clock::get(), $clock);
34+
}
35+
36+
public function testNativeClock()
37+
{
38+
$this->assertInstanceOf(\DateTimeImmutable::class, now());
39+
$this->assertInstanceOf(NativeClock::class, Clock::get());
40+
}
41+
42+
public function testMockClockDisable()
43+
{
44+
$this->assertInstanceOf(NativeClock::class, Clock::get());
45+
46+
$this->assertInstanceOf(MockClock::class, self::mockTime(true));
47+
$this->assertInstanceOf(NativeClock::class, self::mockTime(false));
48+
}
49+
50+
public function testMockClockFreeze()
51+
{
52+
self::mockTime(new \DateTimeImmutable('2021-12-19'));
53+
54+
$this->assertSame('2021-12-19', now()->format('Y-m-d'));
55+
56+
self::mockTime('+1 days');
57+
$this->assertSame('2021-12-20', now()->format('Y-m-d'));
58+
}
59+
60+
public function testMockClockWithClock()
61+
{
62+
$mockClock = new MockClock();
63+
64+
self::mockTime($mockClock);
65+
66+
$this->assertSame($mockClock, Clock::get());
67+
}
68+
69+
public function testPsrClock()
70+
{
71+
$psrClock = new class() implements ClockInterface {
72+
public function now(): \DateTimeImmutable
73+
{
74+
return new \DateTimeImmutable('@1234567');
75+
}
76+
};
77+
78+
Clock::set($psrClock);
79+
80+
$this->assertInstanceOf(Clock::class, Clock::get());
81+
82+
$this->assertSame(1234567, now()->getTimestamp());
83+
84+
$this->assertSame('UTC', Clock::get()->withTimeZone('UTC')->now()->getTimezone()->getName());
85+
$this->assertSame('Europe/Paris', Clock::get()->withTimeZone('Europe/Paris')->now()->getTimezone()->getName());
86+
87+
Clock::get()->sleep(0.1);
88+
89+
$this->assertSame(1234567, now()->getTimestamp());
90+
}
91+
}

src/Symfony/Component/Clock/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"psr/clock": "^1.0"
2424
},
2525
"autoload": {
26+
"files": [ "Resources/now.php" ],
2627
"psr-4": { "Symfony\\Component\\Clock\\": "" },
2728
"exclude-from-classmap": [
2829
"/Tests/"

0 commit comments

Comments
 (0)