Skip to content

Commit 9d304cf

Browse files
[Clock] A new component to decouple applications from the system clock
1 parent 2894680 commit 9d304cf

15 files changed

+612
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"symfony/asset": "self.version",
5858
"symfony/browser-kit": "self.version",
5959
"symfony/cache": "self.version",
60+
"symfony/clock": "self.version",
6061
"symfony/config": "self.version",
6162
"symfony/console": "self.version",
6263
"symfony/css-selector": "self.version",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
6.2
5+
---
6+
7+
* Add the component
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
* A high-resolution monotonic clock, counted from an arbitrary point in time.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
final class HrClock implements TimeInterface
20+
{
21+
private \DateTimeZone $timezone;
22+
23+
public function __construct(\DateTimeZone|string $timezone = null)
24+
{
25+
if (\is_string($timezone ??= date_default_timezone_get())) {
26+
$this->timezone = new \DateTimeZone($timezone);
27+
} else {
28+
$this->timezone = $timezone;
29+
}
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function now(): \DateTimeImmutable
36+
{
37+
$hrtime = hrtime();
38+
$now = '@'.$hrtime[0].'.'.substr(str_pad($hrtime[1], 9, 0, \STR_PAD_LEFT), 0, -3);
39+
40+
return (new \DateTimeImmutable($now, $this->timezone))->setTimezone($this->timezone);
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function timeArray(): array
47+
{
48+
$now = hrtime();
49+
50+
return [$now[0], (float) ($now[1] / 1E3)];
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function timeFloat(): float
57+
{
58+
return hrtime(true) / 1E9;
59+
}
60+
61+
public function sleep(float $seconds): void
62+
{
63+
if (0 < $s = (int) $seconds) {
64+
sleep($s);
65+
}
66+
67+
if (0 < $us = $seconds - $s) {
68+
usleep($us * 1E6);
69+
}
70+
}
71+
}

src/Symfony/Component/Clock/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2022 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
* A clock that returns always the same date, suitable for testing time-sensitive logic.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
final class MockClock implements TimeInterface
20+
{
21+
private \DateTimeImmutable $now;
22+
23+
public function __construct(\DateTimeImmutable|\DateTimeZone|string $now = null)
24+
{
25+
if (\is_string($now ??= 'UTC')) {
26+
try {
27+
$now = new \DateTimeZone($now);
28+
} catch (\Exception) {
29+
$now = new \DateTimeImmutable($now);
30+
}
31+
}
32+
33+
$this->now = $now instanceof \DateTimeImmutable ? $now : new \DateTimeImmutable('now', $now);
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function now(): \DateTimeImmutable
40+
{
41+
return clone $this->now;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function timeArray(): array
48+
{
49+
$now = explode('.', $this->now->format('U.u'));
50+
51+
return [(int) $now[0], (float) $now[1]];
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function timeFloat(): float
58+
{
59+
return (float) $this->now->format('U.u');
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function sleep(float $seconds): void
66+
{
67+
$now = explode('.', $this->now->format('U.u'));
68+
69+
if (0 < $s = (int) $seconds) {
70+
$now[0] += $s;
71+
}
72+
73+
if (0 < ($us = $seconds - $s) && 1E6 <= $now[1] += $us * 1E6) {
74+
++$now[0];
75+
$now[1] -= 1E6;
76+
}
77+
78+
$datetime = '@'.$now[0].'.'.str_pad($now[1], 6, 0, \STR_PAD_LEFT);
79+
$timezone = $this->now->getTimezone();
80+
81+
$this->now = (new \DateTimeImmutable($datetime, $timezone))->setTimezone($timezone);
82+
}
83+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
* A clock that returns the system time.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
final class NativeClock implements TimeInterface
20+
{
21+
private \DateTimeZone $timezone;
22+
23+
public function __construct(\DateTimeZone|string $timezone = null)
24+
{
25+
if (\is_string($timezone ??= date_default_timezone_get())) {
26+
$this->timezone = new \DateTimeZone($timezone);
27+
} else {
28+
$this->timezone = $timezone;
29+
}
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function now(): \DateTimeImmutable
36+
{
37+
return new \DateTimeImmutable('now', $this->timezone);
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function timeArray(): array
44+
{
45+
$now = gettimeofday();
46+
47+
return [$now['sec'], (float) $now['usec']];
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function timeFloat(): float
54+
{
55+
return microtime(true);
56+
}
57+
58+
public function sleep(float $seconds): void
59+
{
60+
if (0 < $s = (int) $seconds) {
61+
sleep($s);
62+
}
63+
64+
if (0 < $us = $seconds - $s) {
65+
usleep($us * 1E6);
66+
}
67+
}
68+
}

src/Symfony/Component/Clock/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Clock Component
2+
===============
3+
4+
Symfony Clock enables decoupling applications from the system clock.
5+
6+
Resources
7+
---------
8+
9+
* [Documentation](https://symfony.com/doc/current/clock.html)
10+
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
11+
* [Report issues](https://github.com/symfony/symfony/issues) and
12+
[send Pull Requests](https://github.com/symfony/symfony/pulls)
13+
in the [main Symfony repository](https://github.com/symfony/symfony)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\HrClock;
16+
17+
class HrClockTest extends TestCase
18+
{
19+
public function testNow()
20+
{
21+
$clock = new HrClock();
22+
$before = new \DateTimeImmutable(sprintf('@%.6f', hrtime(true) / 1E9));
23+
usleep(10);
24+
$now = $clock->now();
25+
usleep(10);
26+
$after = new \DateTimeImmutable(sprintf('@%.6f', hrtime(true) / 1E9));
27+
28+
$this->assertGreaterThan($before, $now);
29+
$this->assertLessThan($after, $now);
30+
}
31+
32+
public function testConstruct()
33+
{
34+
$clock = new HrClock('UTC');
35+
$this->assertSame('UTC', $clock->now()->getTimezone()->getName());
36+
37+
$tz = date_default_timezone_get();
38+
$clock = new HrClock();
39+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
40+
41+
$clock = new HrClock(new \DateTimeZone($tz));
42+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
43+
}
44+
45+
public function testTimeArray()
46+
{
47+
$clock = new HrClock();
48+
$before = hrtime(true) / 1E9;
49+
usleep(10);
50+
$now = $clock->timeArray();
51+
usleep(10);
52+
$after = hrtime(true) / 1E9;
53+
54+
$this->assertSame([0, 1], array_keys($now));
55+
$this->assertGreaterThan($before, $now[0] + $now[1] / 1E6);
56+
$this->assertLessThan($after, $now[0] + $now[1] / 1E6);
57+
}
58+
59+
public function testTimeFloat()
60+
{
61+
$clock = new HrClock();
62+
$before = hrtime(true) / 1E9;
63+
usleep(10);
64+
$now = $clock->timeFloat();
65+
usleep(10);
66+
$after = hrtime(true) / 1E9;
67+
68+
$this->assertGreaterThan($before, $now);
69+
$this->assertLessThan($after, $now);
70+
}
71+
72+
public function testSleep()
73+
{
74+
$clock = new HrClock();
75+
$tz = $clock->now()->getTimezone()->getName();
76+
77+
$before = hrtime(true) / 1E9;
78+
$clock->sleep(1.5);
79+
$now = $clock->timeFloat();
80+
usleep(10);
81+
$after = hrtime(true) / 1E9;
82+
83+
$this->assertGreaterThan($before + 1.5, $now);
84+
$this->assertLessThan($after, $now);
85+
$this->assertLessThan(1.9, $now - $before);
86+
$this->assertSame($tz, $clock->now()->getTimezone()->getName());
87+
}
88+
}

0 commit comments

Comments
 (0)